This is a dead simple example for starting with grails web flow
I'm writing this little invoice printing application for my own company. I don't write the application because this is the most cost effective way to get an application that prints my invoices. But writing this application gives me the chance to try new technologies like Groovy and Grails in a real life scenario, and it gives me exactly the (modest) application that I want. In this application I have a little web flow of preparing an invoice, but because I didn't have any experience with spring web flow I decided to work out a simple example this little tutorial is the result. This tutorial is about the well known number guess example. I did not look at other examples so there might be some differences with other implementations. This example application is comparable to the numberguess example of Jboss Seam. For this tutorial it is to required to have Grails (I used Grails 1.0-RC1) set up and to have basic grails knowledge. The example exists of three pages the guess page where a number can be inserted a win and loose page with a try again button that resumes the game. And one controller the NumberGuessController that holds the flow definitions. The directory structure below shows the four files. 
Create an application with the grails create-app commando: grails create-app grailswebflowdemo Create a controller with the create-controller: grails create-controller numberGuess Locate the controller under grails-app/controllers. Add the default “view” to your controller | def index = { render(view:"game") } | this will start the game flow if you access the numberguess controller. Create the game flow. The naming convention is <name>Flow. | def gameFlow = { def numberOfGuesses def numberToGuess start(){ action{ numberOfGuesses = 5 numberToGuess = new Random().nextInt(100) log.debug("number to guess ${numberToGuess}") } on("success").to "guess" } guess{} } |
The gameFlow has two instance variables and an action state start that sets the numbers to respectively a random number that has to be guessed and the number of guesses. On success the start action state directs to the view state guess. So for the view state we have to create a gsp page. A view state renders automatically a view with the same name as the state. Create a directory "name" (the same name as the flow) under the directory 'grails-app/views/numberGuess'. Create the guess page in the game directory like this: | <html> <body> <g:form action="game"> <input type="text" name="number" /> <g:submitButton name="submit" value="click"/> </g:form> </body> </html> | As you can see the forms action has the same name as the flow. The submit button's name is the name of the event that is triggered from this view. We will come to this in a moment. Start your embedded grails server ??? and access the url of the NumberGuessController. http://localhost:8080/grailswebflowdemo/numberGuess As you see this directs you to the guess page. If everything went allright the numberguess controllers index pointed to the gameFlow. The gameFlow started the start action state this state assigned the values and directed to the guess view state. To see the prove of the start state executing. And provide you with the numberTo Guess later on in the tutorial so you can test the application. We will set the logging level for the controller to debug. Go to the grails-app/config directory and change the Config.groovy from rootLogger="error,stdout" logger { grails="error,stdout" | to rootLogger="debug,stdout" logger { grails.'NumberGuessController'="debug, stdout" grails="debug,stdout" | if you access the url above again and you look at the logging it says something [1243828] controller.NumberGuessController number to guess 69 Back to the guess page, if we push submit button we want the application to do the following things. First the application has to determine if the number guessed is alright. If the number is correct the flow forwards to the win page. If the number is not guessed right it has to determine whether the player has guesses left if yes go to the guess page if not go to the loose page. As you can see on the guess page there is submit event triggered from the guess page. To handle this submit event we change the guess view state to guess{ on("submit"){ flow.guessedNumber = Long.parseLong(params.number) }.to "evaluateGuess" } | On the trigger submit the number you guess will be put on a variable of the flow. And it forwards to the action state that will evaluate you guess: evaluateGuess{ action { if(numberToGuess == flow.guessedNumber){ return win() }else{ return evaluateNumberOfGuesses() } } on("win").to "win" on("evaluateNumberOfGuesses").to "evaluateNumberOfGuesses" } evaluateNumberOfGuesses{ action{ numberOfGuesses-- if(numberOfGuesses > 0){ return tryAgain() }else{ return loose() } } on("tryAgain").to "guess" on("loose").to "loose" } win(){ on("playAgain").to "start" } loose(){ on("tryAgain").to "start" } } | As you can see the evaluateGuess action state forwards to the win view state or to the evaluateNumberOfGuesses action state this in turn forwards to the loose or guess view states. Add the win.gsp and loose.gsp in the game directory. Below are the the win and loose pages. <html> <body> <h1>You win</h1> <g:link action="game" event="playAgain" >Play again</g:link> </body> </html> | <html> <body> <h1>You loose</h1> <g:link action="game" event="tryAgain" >Try again</g:link> </body> </html> | In these pages there is no form but the g:link tag can be used to trigger the tryAgain and playAgain events in the win and loose view states. Both will forward to the start action state For completeness here is the complete code for the NumberGuessController. class NumberGuessController { def index = { render(view:"game") } def gameFlow = { def numberOfGuesses def numberToGuess start(){ action{ numberOfGuesses = 5 numberToGuess = new Random().nextInt(100) log.debug("number to guess ${numberToGuess}") } on("success").to "guess" } guess{ on("submit"){ flow.guessedNumber = Long.parseLong(params.number) }.to "evaluateGuess" } evaluateGuess{ action { if(numberToGuess == flow.guessedNumber){ return win() }else{ return evaluateNumberOfGuesses() } } on("win").to "win" on("evaluateNumberOfGuesses").to "evaluateNumberOfGuesses" } evaluateNumberOfGuesses{ action{ numberOfGuesses-- if(numberOfGuesses > 0){ return tryAgain() }else{ return loose() } } on("tryAgain").to "guess" on("loose").to "loose" } win(){ on("playAgain").to "start" } loose(){ on("tryAgain").to "start" } } } | Note that the flow does not end after loosing and retrying.
|