Grails webflow - converting action based to web flow - grails

Our app is working using the normal actions in the controller, but there are some difficulties in going backward from one page to another. I've been tasked with converting this to use web flows instead and I'm hitting all kinds of road blocks. Suggestions and insights greatly appreciated.
The controller has actions for list, view, create, sign and print. I've done this:
def index = {
redirect (action: "someFlow")
}
def someFlow = {
init {
// some object settings
}
on("success").to("list")
list {
}
on("create").to "create"
on("view).to "view"
create {
}
on("next").to "sign"
on("cancel).to "list"
view {
}
on("edit").to "create"
on("back").to "list"
sign {
}
on("done").to "list"
on("back").to "create"
edit {
}
on("done").to "view"
}
When I trace through this with the debugger it appears to hit every state in succession without doing anything or stopping on any page. Then it goes back to the list state.
If I click the link for 'create' I get a web page telling me the "resource (/directory path/create) is not available."
But if it could find the list.gsp, why can't it find the create.gsp?
I feel like I'm groping blindly in the dark; none of the books seems to address any of this and I can't find any other resources to indicate why it behaves this way. Anyone have an idea?
Thanks.

The "on" statements need to be inside of the closures. For example, this:
list {
}
on("create").to "create"
on("view").to "view"
should be this:
list {
on("create").to "create"
on("view").to "view"
}
That should at least fix the issue with flying through the whole flow. As for the GSPs not being found, you'll need to create a folder (named "some") in the folder for your controller. Place the GSPs for each of the actions in your flow inside here.
That being said, I agree with Rob that it doesn't really seem like a good candidate for a webflow.

Related

Grails multiple pages form submit

I am using Grails for my project.
There will be a lot of forms across multiple pages and using next and previous to navigate.
Also need to provide the function of save as a draft.
Are there any good way to do this?
Grails provide webflows to make this kind of form wizards. There's also a detailed guide about this in the documentation. Example:
class BookController {
…
def shoppingCartFlow ={
showCart {
on("checkout").to "enterPersonalDetails"
on("continueShopping").to "displayCatalogue"
}
…
displayCatalogue {
redirect(controller: "catalogue", action: "show")
}
displayInvoice()
}
}
Here you have a flow with 3 steps: showCart, displayCatalogue and displayInvoice. You can store objects that will live in the entire flow without beign persisted in the database yet.

Create "pretty" user profile URLs in Grails application

Anybody who already have implemented something similar using Grails could tell me please which are the good pratices (if there are any) to create user profile URLs with the format "http://www.myservice.com/username", as in Facebook, Twitter, Linkedin?
I'm trying to implement it through the UrlMappings and appears to me I'll need to break with the code conventions, at least for the Controllers.
So, any suggestions are welcome, thanks.
UPDATE 1
When I mentioned my concern about breaking the code conventions, what I'm saying is that I want to show the user profile using this mapping, but I do have other objects in my application which I would like to access using the default mapping:
"/$controller/$action?/$id?"()
SOLUTION
Thanks to the great contributions I've received here, I've camed up with this solution, which solves my problem.
As was pointed out, to do this kind of mapping, I'll need to control more closely how my requests are handled. That means I'll need to tell to Grails which controllers I don't want to be mapped to the "username" rule.
Since that will be a very tedious task (because I have several controllers), I did this to automate it:
UrlMappings.groovy
static mappings = {
getGrailsApplication().controllerClasses.each{ controllerClass ->
"/${controllerClass.logicalPropertyName}/$action?/$id?"(controller: controllerClass.logicalPropertyName)
}
"/$username/$action?"(controller: "user", action: "profile")
}
...
}
And of course, I'll need to do something similar in my user registration process to avoid usernames to be equal to some controller name.
That's it, thank you all.
Assuming you have a UserController and you are going to map any domain.com/username to the show action of user controller, your url mapping could be something like this :
In my example, name will become a parameter in your params.
for further details refer to here
Hope this helps.
static mappings = {
"/$name"(controller: "user", action: "show")
...
}
Given your requirements, everything after http://yourdomain.com/ can be a username or one of your other controllers, which can have undesired effects depending on which url mapping is defined first (e.g. user controller vs nonuser controller). But most likely the nonuser controller list will be the smaller list, so you should place that first and filter against it, then treat all other url mappings as user mappings.
Here is an example:
static mapping = {
"/$controller/$action?/$id?" {
constraints {
controller inList: ['nonUserController1', 'nonUserController2',...]
}
}
//this should work for /username and /username/whateveraction
"/$username/$action?"(controller: 'user')
}
Some things to note here:
you need to place the non user controller url mapping first, since everything else after http://yourdomain.com/ may be a username - correct?
second you need to place a constraint to catch all the non user controllers, while ignore the user url mappings
also you need to prevent a user from signing up with a username that matches one of your non user controllers

Grails view URL Mapping

I'm kind of new to grails and I'm trying to just map a basic URL request to a view.
So, say I have a view, /x/index.gsp and I want the user to be able to go to it. There will also be /y/index.gsp, /z/index.gsp, etc.
I defined it like so:
"/$customer/index" { view = {params.customer+"/index"} }
This seems to throw an exception though. I also have :
"/$customer/$controller/$action?/$id?" { }
which does work and I don't want to have to create a controller that doesn't really do anything but handle the index call and show it.
I'm sure I'm missing something simple but I don't know what it is.
The reason the first mapping fails is because it can't figure out what controller to route the request to.
To fix it, you need to define what controller you want the top mapping to route to. This is how I did this in a recent project of mine:
"/uploaders/$id" {
controller: "uploader"
}
To map to just a view:
"/$customer/index"(view: "/${params.customer}/index")

Grails Web Flow: Redirect to the webflow from another controller action?

Here's what I think I want to do:
class MyController {
def goToWizard = {
if (params.option1)
redirect actionName:'wizard1', params:params
if (params.option2)
redirect actionName:'wizard2', params:params
}
def wizard1Flow = {
start {
action {
// put some values from the params into flow scope
[thingsThatGotPassedIn:params.thingsThatGotPassedIn]
}
on('success').to 'nextThing...'
}
// wizard 1 implementation...
//...
done {
redirect view:'somewhereElse'
}
}
def wizard2Flow = {
start {
action {
// put some values from the params into flow scope
[thingsThatGotPassedIn:params.thingsThatGotPassedIn]
}
on('success').to 'nextThing...'
}
// wizard 2 implementation...
//...
done {
redirect view:'somewhereElse'
}
}
}
I've tried something like this, but I don't seem to ever get into the webflow. Is this a valid approach?
The reason for all this is that I have a gsp that looks like so (a form with 2 submit buttons inside, each one should trigger a different webflow)
<g:form action="goToWizard">
...
<g:submitButton name="wiz1" value="Goto Wizard1"/>
<g:submitButton name="wiz2" value="Goto Wizard2"/>
</g:form>
There are some input elements inside the form that I want to pass the values of into whichever webflow gets called. I'd rather just have the form submit call the appropriate webflow directly (the way all the examples that I've seen work), but there are two webflows, and only one form. How can I accomplish this?
I'm also interested in alternative implementations, if you think this is the wrong way. I'm new to webflows in grails.
Take a look at actionSubmit tag in grails documentation. I think, you should use actionSubmit instead of submitButton
actionSubmit creates a submit button that maps to a specific action, which allows you to have multiple submit buttons in a single form. Javascript event handlers can be added using the same parameter names as in HTML.
By this approach, You no need to mention action in form tag, i.e. No need to make a check in goToWizard. You can send contents directly to your particular action.
Is this the solution to your problem?

How to I render validation results in a Grails Web Flow?

I have a Grails WebFlow that is similar to the following example:
def myFlow = {
init {
action {
def domain = MyDomain.get(params.id)
flow.domain = domain ? domain : new MyDomain()
}
on('success').to 'first'
}
first {
on('continue') {
flow.domain.properties = params
if(!flow.domain.validate()) {
return error()
}
}.to 'second'
}
...
}
Given this example, if a validation error occurs in the transition on('continue') in first:
What's the preferred way to set the model with the invalid domain object so I can use a <g:hasErrors>... in my view (like I would in a normal controller action)?
When I call error(), does it send the flow back to init or to first?
Does error() take any arguments (i.e. a model) that can be used for what I'm trying to accomplish (I can't find much documentation on the error() method).
I'd also take suggestions on how I could improve my flow states to better-facilitate handling these validation errors.
Summary: What's the preferred way to render validation errors within a Grails Web Flow?
-1
What's the preferred way to set the
model with the invalid domain object
so I can use a ... in my
view (like I would in a normal
controller action)?
You just need to return your domain object that has the errors. you can do that in an
action state
action {
user.validate()
return [user:user]
}
You can also set your errors in flash scope. On each transition Grails will copy the content of the flash scope into the ModelView and thus available in your gsp page
action {
flash.error = "your message"
}
-2
When I call error(), does it send the flow back to init or to
first? When you call error it call the
transition you defined for
You should define a handler for such as
on("error").to("handlerError")
Does error() take any arguments (i.e. a model) that can be used for
what I'm trying to accomplish (I can't
find much documentation on the error()
method).
I don't think so but you can do the following to set any variable when transitioning from one state to another
on("error") {
// do Something
}.to("handlerError")
3-
I'd also take suggestions on how I could improve my flow states to
better-facilitate handling these
validation errors.
I use flash for global and form errors but i needed ONE way to deal it. Currently with Grails the flash scope is managed differently in a flow than it's managed in a normal action. So I decided to write a little plugin to change the way the flash scope is handled in a flow and make it consistent with the way is managed in a normal action.
I a gsp page i can use my tags in the follwing way regardless of the action type (normal or flow)
<message:global />
or
<message:inline />
As for Form fields errors, i didn't like to deal with errors in domain objects. I wanted something more unified. So I decided to make them part of the http protocol and I have a javascript component the inject them into the form if i opt to. I found this solution much cleaner than dealing with g:errors each time.
-ken
I've found that one way to do it is to specifically invoke render() in the transition state, providing the model. Here's an example where only one field is validated:
first {
render(view: 'info', model: [flow.domain])
on('continue') {
if(!flow.domain.validate(['myField'])) {
return error()
}
}.to 'second'
}
One can then use <g:hasErrors> as follows:
<g:hasErrors bean="${domain}" field="myField">
<g:renderErrors bean="${domain}" as="list" field="myField"/>
</g:hasErrors>

Resources