Presenter: Joseph Nusairat, Integrallis About This Talk
- discuss web flows
- what are spring web flows
- show some basics of webflow
- live demo
- Grails web flows didn’t really mature until Grails 1.1.1
What is Web Flow
- allows for controlling the flow of items via steps, substeps, other flows
- use when you want to control the sequence in which events occur in a series of events
- e.g. buying a plane ticket on travelocity–goes through a specific series of steps
- can’t go to point D without going through A, B, and C first
- also support branches
- control is not on the page but in a DSL
- can keep components open for a longer period of time
- where do you store the information for something like an uncompleted plane ticket order?
- can store in the database, but would require cleanup for things that never make it to checkout
- general way people have done this is via session data
- check out spring memory management session recording on infoq
- don’t want to use up a lot of your server RAM with sessions that are unused
- at the end of the flow, data is persisted to a database or somewhere more permanent
- when you remove the flow aspect of things from the pages themselves, it makes things more flexible and configurable
- SEAM allows for different timeouts for conversations and sessions themselves
WebFlows in Spring
- was new in Spring 2.0
- great integration with JSF for rich UI
- allows control over the scoping of objects for a limited period of time
- cannot have multiple conversations
- uses xml
- xml is great for transferring objects, but not great for configuration
- configurations aren’t generally that black and white
Creating a WebFlow
- WebFlow in 1.2 M3 doesn’t work at the moment
- GRAILS-5185
- demo will be in 1.1.1
- in 1.2 WebFlow is a plugin (grails install-plugin webflow)
- in 1.1.1 it’s built in, but in 1.2 you have to install the plugin to use it
- flows live in the controller
- look just like an action, except ends with “Flow”
- e.g. def bookingCartFlow = {}
- look just like an action, except ends with “Flow”
Defining a View
- views represent the individual pages
- demo of hotel booking flow
- “do you want to pay via paypal?” — decision point with branch
- def bookingCartFlow = {
start() {}
}- whatever is defined first is the starting point–doesn’t have to be called start
- DSL used to direct the flow, e.g. on(“submit”).to “end”
Domain Model
- don’t have to worry too much about the domain model except for the object that’s used to track the conversation
- Booking class is what’s used to track the conversation and isn’t persisted until the end
- must implement Serializable in order to work
Creating a Controller and Actions
- create a controller as per usual, but create an action ending with “Flow”
- def bookingCartFlow = {
start() {
on(“next”) {
// do work here
log.info “inside start”
}.to “stepA”
on(“done”).to “end”
} stepA() {
on(“prev”).to “start”
on(“done”).to “end”
} end() {
// define action state
action {
log.info “finish what you’re done with”
}
}
} - make sure to follow the DSL–doesn’t necessarily error out if you aren’t following the DSL
- examples here are all done in the controller but in a real app you’d be using a service layer
- according to the docs transactional needs to be set to false, but it doesn’t work this way currently (1.1.1)
- can do with transaction inside services if you’re trying to save states in a particular order
Views
- in flows you can have subfolders below the view directory for the controller
- class HotelController {
def index = { redirect(action:’bookingCart’) } // this will go to the bookingCartFlow def paypalFlow = {
pay() {
on(“submit”) {
log.info “Was it paid? ${params.paid}”
}.to “evaluatePayment”
} evaluatePayment() {
action {
if (params.paid == ‘yes’) {
conversation.booking.paid = true
return paidInFull
} else {
return paymentDeclined()
}
}
}
} def bookingCartFlow = {
start() {
action {
log.info = “-start the booking-“
}
on(“success”).to “findHotels”
} findHotels() {
on(“showHotels”) {
// find the hotel
def hotels = Hotel.findAllByNameILike(“%${params.hotelName}%”, [max:10])
[hotelResults : hotels]
}.to “hotelListPage”
} hotelListPage() {
on(“view”) {
flow.hotel = Hotel.get(params.id)
}.to “hotelView” on(“back”) {
}.to “findHotels”
} hotelView() {
on(“back”).to “start”
on(“book”).to “bookingPage”
} bookingPage() {
on(“proceed”) {
def booking = new Booking(params)
booking.hotel = flow.hotel
conversation.booking = booking
if (!booking.validate()) {
return error() // returns back to the page they came from and outputs errors
}
}.to “saveBooking”
} saveBooking() { // the parens here are optional
// no corresponding view to “saveBooking” so this is a decision node
action {
if (params.paymentType == “creditCard”) {
return creditCard()
} else {
return paypal()
}
}
on(“creditCard”).to “creditCard”
on(“paypal”).to “paypal”
} paypal() {
subflow(paypalFlow)
on(“paidInFull”)
} creditCard() {}
}
} - to identify an event in a flow, you can use the name of a g:button as the event you want to trigger when the button is clicked
- to use an event in a g:link, add event parameter to g:link, e.g. <g:link event=”view” … />
Scopes Available in Flows
- request
- params
- session
- flash
- flow
- lasts for the duration of the flow
- removed when flow ends
- conversation
- lasts for the length of the flow and nested subflows
- subflows are good for things like different payment methods–can be reused across multiple flows
- best practice point: don’t refer to anything about flows or conversations in gsps
- this ties the view to a specific flow or conversation and reduces the ability to reconfigure and reuse things
What are subflows
- ability to create alternative flow paths
- call subflows via flows
- are their own flows themselves
- name with “Flow” at the end
- calling a subflow
- subflow(subflowName)
Can you call to flows in different controllers?
- yes, but it gets a bit funky
- define flow in another controller as per usual, but declare the flow as static
- in other controller, do def creditcardFlow = BillingController.creditcardFlow
- still have to put the billing views under the hotel views as opposed to the billing views
How do you end the flow?
- if a flow doesn’t do a transition or a redirect, the flow ends
Final thoughts
- don’t call services with transactions–can cause unexpected problems
- in grails 1.2 flows are a plugin
- webflow moves objects from flash to request scope between transition states
- don’t include the scope prefix when calling on the page
- merge flow/conversation to local scopes