View states

A view state is a one that doesn't define an action or a redirect. So for example the below is a view state:

enterPersonalDetails {
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

It will look for a view called grails-app/views/book/shoppingCart/enterPersonalDetails.gsp by default. Note that the enterPersonalDetails state defines two events: submit and return. The view is responsible for triggering these events. If you want to change the view to be rendered you can do so with the render method:

enterPersonalDetails {
   render(view:"enterDetailsView")
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

Now it will look for grails-app/views/book/shoppingCart/enterDetailsView.gsp. If you want to use a shared view, start with a / in view argument:

enterPersonalDetails {
   render(view:"/shared/enterDetailsView")
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

Now it will look for grails-app/views/shared/enterDetailsView.gsp

Action States

An action state is a state that executes code but does not render any view. The result of the action is used to dictate flow transition. To create an action state you need to define an action to to be executed. This is done by calling the action method and passing it a block of code to be executed:

listBooks {
   action { 
	  [ bookList:Book.list() ]
   }
   on("success").to "showCatalogue"
   on(Exception).to "handleError"
}

As you can see an action looks very similar to a controller action and in fact you can re-use controller actions if you want. If the action successfully returns with no errors the success event will be triggered. In this case since we return a map, this is regarded as the "model" and is automatically placed in flow scope.

In addition, in the above example we also use an exception handler to deal with errors on the line:

on(Exception).to "handleError"

What this does is make the flow transition to a state called handleError in the case of an exception.

You can write more complex actions that interact with the flow request context:

processPurchaseOrder  {
     action {
         def a =  flow.address
         def p = flow.person
         def pd = flow.paymentDetails
         def cartItems = flow.cartItems
         flow.clear()

def o = new Order(person:p, shippingAddress:a, paymentDetails:pd) o.invoiceNumber = new Random().nextInt(9999999) cartItems.each { o.addToItems(it) } o.save() [order:o] } on("error").to "confirmPurchase" on(Exception).to "confirmPurchase" on("success").to "displayInvoice" }

Here is a more complex action that gathers all the information accumulated from the flow scope and creates an Order object. It then returns the order as the model. The important thing to note here is the interaction with the request context and "flow scope".

Transition Actions

Another form of action is what is known as a transition action. A transition action is executed directly prior to state transition once an event has been triggered. A trivial example of a transition action can be seen below:

enterPersonalDetails {
   on("submit") {
       log.trace "Going to enter shipping"	
   }.to "enterShipping"
   on("return").to "showCart"
}

Notice how we pass a block of the code to submit event that simply logs the transition. Transition states are extremely useful for data binding and validation, which is covered in a later section.