One of the little 'gotchas' that I ran into was how to properly complete the flow. Now, reading up the documentation, it would seem easy - at the end of your wizard/flow, you just redirect to a different controller+action and it's all good. It all makes sense - often time, the wizard walks through multiple steps, collects some information, and when it's all done (you save your brand new Foo), you can just redirect to the details page for foo (e.g. /foo/show/1).
Well you thought it would be that easy. Not that quick...
The Grails web flow documentation is kinda deceptive like that. It shows you a simplistic example that works; however, when you try to do something more realistic, then you start getting into trouble. So, the example form the docs looks like this:
def fooFlow = {
def startState { }
def endState {
redirect(controller:'foo', action:'bar')
}
}
The catch is that in their examples, the redirect is to a static URL that doesn't take any parameters.The problem comes up when in the end state you try to pass in some parameter from the flow (e.g. which often is the case e.g. at the end of the flow you want to display the details page (e.g. redirect(controller:'foo', action:'bar', id:flow.fooId) . The problem manifests itself in a weird way - the web flow would store a particular value of flow property( e.g. flow.fooId (under onditions that I couldn't figure out), and even though your current wizard might have stored a particular value from the current flow, for whatever reason it ends up redirecting to a value stored from a previous flow. So, the wizard 'kinda' worked in that it redirected to a details page at the end of the wizard, but in a large percentage of the time, it would redirect to the wrong details page. From what I could gather, the issue is that in the end state, the redirect cannot use any values from the flow, session, or flash and as a result uses some cached value (possibly from the first flow execution)
The solution to this (which is somewhere on the Grails mailing lists) is as follows: add an explicit empty "end" state (including an empty gsp to match the end state name), and in the states that transition into the end flow, issue the redirect from the penultimate state, e.g.
def fooFlow = {
def startState { }
def beforeEnd {
action {
redirect(controller:'foo', action:'bar', id:flow.fooId)
}
on("success").to "end"
}
def end(
/** note that this requries an end.gsp to be present
in the flow subdirectory, but it never gets rendered after the
redirect **/
)
}
Now, with this trick at hand, the end.gsp never gets rendered, and the client browsers do get redirected to the detail pages that you want to display, outside of your web flow.
As a more Web Flow centric alternative, you could always store the relevant object (Foo) inside the flow and display any relevant details about the object in the end state (end.gsp)
This is excellent advice, something I was struggeling with just yesterday!
ReplyDeleteThank you so much for this post.
ReplyDeleteI've been struggling for 10s of such kind of magic in Grails.
I found that at many point, it works well most of the time and finally found out that there are some case it does not work. And sometime have no solution at all.
Imagine if your project have to be finished in next 2 weeks and you cannot wait until the new version of Grails or you cannot migrate to the new Grails release. Huuuh.
I feel that it's hard to rely on junior programmer to handle development in Grails project because to discover magic takes quite a lot of experience and analytic skill. Anyhow, it takes time and often unpredictable long time. Is this mean Grails is for senior only?