Your SlideShare is downloading. ×
Grails webflow Ivo Houbrechts       Ixor
About Ixor•   Since 2002•   40 experienced ICT professionals•   Software development for the JVM•   Consultancy and produc...
Agenda• Grails webflow plugin   • Demo app   • Spring webflow framework   • Flow states   • Subflows   • Scopes   • Ajax  ...
Demo app• Simple domain model:    • Project    • Developer    • UserStory• Wizard for creating a project
Spring webflow framework• Manage page flows  • Wizards• Manage objects  • Scopes• Manage states• Back-button hell• Grails ...
Defining a grails webflowdef newProjectWizardCleanedUpFlow = {        onStart doOnStart       projectInfo {           on("...
Flow states• View states• Action states• Start and end states• Subflow states
View state: definition• Renders a view• Definition:    projectInfo {        on("next").to("lead")    }• Default view   /co...
View state: events• Triggered by user  • Link     <g:link action="newProjectWizard” event="remove">Remove</g:link>  • Butt...
View state: transition actions• Used for binding and validation   on("next") {       flow.projectInstance.properties = par...
Action state: definitionsaveProject {    action {        if (flow.projectInstance.save()) {             end()        } els...
Action state: events• Events  • Explicit     • call missing method  • Implicit     • success     • exception  saveProject ...
Action state: returning a model  saveProject {      action {          def project= projectService.save(flow.projectInstanc...
Start and end states• Start state: first state in flow definition• End state  • Empty       cancel()  • Redirect     end {...
Subflow states• Like method calls  • Reusable (parts of) flows• Definition    lead {        subflow(controller: "developer...
Subflow input (grails 1.4.0)• Declare input arguments  • Like method arguments (contract)   def getDeveloperFlow = {      ...
Subflow input (grails 1.4.0)• Required arguments  • Exception if not provided  • Not possible to use flow standalone• Defa...
Subflow output (grails 1.4.0)• Define output in end state  selected {      output {          developer {flow.developer}   ...
Subflow input and output values• Constant values  • Defined at flow definition time (application startup)  • Refer to (con...
Scopes• Standard scopes• Webflow scopes  • flow     • flow execution, only visible in one flow     • serialized  • convers...
Ajax• Define transition to current state  • Render template in transition action   stories {       on("remove") {         ...
Testing• Integration test flows  class ProjectControllerTests extends WebFlowTestCase {      @Override Object getFlow() { ...
Tip 1• Returning flow output to other controller actions  • Use standard grails flash scope   RequestContextHolder.current...
Tip 2• Create clean readable flow definitions     • Use closure variables def newProjectWizardCleanedUpFlow = {         on...
Tip 3• Flow diagrams (intellij)  • Demo app: grails generate-xml-flow-definitions
Tip 4• Kick start flow views  • grails generate-views  • Change     • action attribute in forms and links     • explicitly...
Tip 5• Bread crumbs  • <flow:breadCrumbs/>  • Tag source code: see demo app
Pitfalls• Implement java.io.Serializable  • Objects in flow and conversation scope  • Events don’t get fired        • Miss...
Alternatives• Ad hoc flows  • Where to store state?  • What if user quits in the middle and starts again?• One-page wizard...
Extended validation plugin• Validation for non-grails domain classes  • Particularly useful in combination with webflow• C...
Extended validation plugin• Constraint groups   static constraints = {       invoiceData {           customer(cascade: tru...
Livesnippets• groovy & grails related documentation  • Demo application  • Code snippets     • Directly linked to demo cod...
Questions  ?
Upcoming SlideShare
Loading in...5
×

GR8Conf 2011: Grails Webflow

4,749

Published on

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,749
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
0
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Transcript of "GR8Conf 2011: Grails Webflow"

  1. 1. Grails webflow Ivo Houbrechts Ixor
  2. 2. About Ixor• Since 2002• 40 experienced ICT professionals• Software development for the JVM• Consultancy and product development• Loyal customers in Finance, Telecom and Government• www.ixor.be
  3. 3. Agenda• Grails webflow plugin • Demo app • Spring webflow framework • Flow states • Subflows • Scopes • Ajax • Testing • Tips & pitfalls • Alternatives• Extended validation plugin• Questions
  4. 4. Demo app• Simple domain model: • Project • Developer • UserStory• Wizard for creating a project
  5. 5. Spring webflow framework• Manage page flows • Wizards• Manage objects • Scopes• Manage states• Back-button hell• Grails webflow plugin • Subset of features
  6. 6. Defining a grails webflowdef newProjectWizardCleanedUpFlow = { onStart doOnStart projectInfo { on("next", doBindProjectInfo).to("lead") } lead { subflow(controller: "developer", action: "getDeveloper", input: [experience: Experience.SENIOR]) on("selected", doSetLead).to(selectLeadOrTeam) on("cancel", doFlashLeadMandatoryMessage).to("lead") } team { on("add").to("addTeamMember") on("remove", doRemoveTeamMember).to("team") on("next").to("stories") } addTeamMember { subflow(controller: "developer", action: "getDeveloper”) …
  7. 7. Flow states• View states• Action states• Start and end states• Subflow states
  8. 8. View state: definition• Renders a view• Definition: projectInfo { on("next").to("lead") }• Default view /controllerName/flowName/viewstateId• Override with render(view: "myview")
  9. 9. View state: events• Triggered by user • Link <g:link action="newProjectWizard” event="remove">Remove</g:link> • Button <g:form action="newProjectWizard"> <g:submitButton name="add" value="Add developer"/> </g:form>• Transition team { on("add").to("addTeamMember") on("remove").to("team") }
  10. 10. View state: transition actions• Used for binding and validation on("next") { flow.projectInstance.properties = params if (!flow.projectInstance.validate(["name", "description"])) { error() } }.to("lead") • Supports command objects• Dynamic transitions on("selected“).to { if (!flow.projectInstance.validate(["lead"])) { return "lead" } else { return "team" } }
  11. 11. Action state: definitionsaveProject { action { if (flow.projectInstance.save()) { end() } else { projectInfo() } } on("end").to("end") on("projectInfo").to("projectInfo")}
  12. 12. Action state: events• Events • Explicit • call missing method • Implicit • success • exception saveProject { action { def project= projectService.save(flow.projectInstance) return [project: project] } on("success").to("end") on("ValidationException").to("retry") on("Exception").to("fatalError") }
  13. 13. Action state: returning a model saveProject { action { def project= projectService.save(flow.projectInstance) return [project: project] } on("success").to("end") on("ValidationException").to("retry") on("Exception").to("fatalError") }• Merged into flow scope• Used in next state or view
  14. 14. Start and end states• Start state: first state in flow definition• End state • Empty cancel() • Redirect end { redirect(action: "show", id: flow.projectInstance.id) }
  15. 15. Subflow states• Like method calls • Reusable (parts of) flows• Definition lead { subflow(controller: "developer", action: "getDeveloper", input: [experience: Experience.SENIOR]) on("selected“).to("team") on("cancel").to("lead") }• Events • Last state of subflow
  16. 16. Subflow input (grails 1.4.0)• Declare input arguments • Like method arguments (contract) def getDeveloperFlow = { input { experience() title(required: true) } …• Provide values in subflow call subflow(controller: "developer", action: "getDeveloper", input: [experience: Experience.SENIOR, title: "Select lead"])
  17. 17. Subflow input (grails 1.4.0)• Required arguments • Exception if not provided • Not possible to use flow standalone• Default values def mySubFlow = { input { foo() // optional input with no default value bazz(required: false, value: someConstantValue) dynamic { flow.someProperty } dynamicBis (value: someNamedClosure) } …
  18. 18. Subflow output (grails 1.4.0)• Define output in end state selected { output { developer {flow.developer} } }…• Retrieve output in calling flow on("selected") { flow.projectInstance.lead = currentEvent.attributes.developer }.to(…)
  19. 19. Subflow input and output values• Constant values • Defined at flow definition time (application startup) • Refer to (controller) variables or literal expressions• Dynamic values • Evaluated at flow execution time • Defined with closures• Applicable in: • Default input values • Input values in subflow call • Output values
  20. 20. Scopes• Standard scopes• Webflow scopes • flow • flow execution, only visible in one flow • serialized • conversation • flow execution, visible for all flows and subflows • Cf. class members ↔ method arguments (subflow input) • serialized • flash • semantics like grails flash, but different instance • merged with request scope → omit ‘flash.’ prefix
  21. 21. Ajax• Define transition to current state • Render template in transition action stories { on("remove") { … render(template: "newProjectWizard/editStories", model: [projectInstance: flow.projectInstance]) }.to("stories") …• Use remoteLink or formRemote <g:remoteLink update="editStories" controller="project“ action="newProjectWizard" event="remove" params="${[name: userStory.name, ajaxSource: true, execution: params.execution]}"> Remove</g:remoteLink>
  22. 22. Testing• Integration test flows class ProjectControllerTests extends WebFlowTestCase { @Override Object getFlow() { return controller.newProjectWizardFlow } protected void setUp() { //register all subflows registerFlow("developer/getDeveloper", new DeveloperController().getDeveloperFlow) } void testHappyFlow() { startFlow() assert "projectInfo" == flowExecution.activeSession.state.id controller.params.name = "project name“ signalEvent("next") assert "x" == flowScope.projectInstance.lead.name …
  23. 23. Tip 1• Returning flow output to other controller actions • Use standard grails flash scope RequestContextHolder.currentRequestAttributes().flashScope.message = "Project was successfully created"
  24. 24. Tip 2• Create clean readable flow definitions • Use closure variables def newProjectWizardCleanedUpFlow = { onStart doOnStart projectInfo { on("next", doBindProjectInfo).to("lead") } … • Define private closures private def doBindProjectInfo = { … }
  25. 25. Tip 3• Flow diagrams (intellij) • Demo app: grails generate-xml-flow-definitions
  26. 26. Tip 4• Kick start flow views • grails generate-views • Change • action attribute in forms and links • explicitly fill in the controller attribute in forms and links when using subflows • add an event attribute to links • remove the flash prefix in flash.message • use submitButton in stead of actionSubmit
  27. 27. Tip 5• Bread crumbs • <flow:breadCrumbs/> • Tag source code: see demo app
  28. 28. Pitfalls• Implement java.io.Serializable • Objects in flow and conversation scope • Events don’t get fired • Missing methods are not missing (f.e. scaffold actions) • Limited execution history • Back navigation • Ajax calls increase state count • Last state is subflow state • Back navigation after flow completes redirects to subflow
  29. 29. Alternatives• Ad hoc flows • Where to store state? • What if user quits in the middle and starts again?• One-page wizards with ajax • One page • One controller action for each step • Where to store state? • Subflows with overlays
  30. 30. Extended validation plugin• Validation for non-grails domain classes • Particularly useful in combination with webflow• Cascade constraint static constraints = { customer(cascade: true) …• Instance constraint static constraints = { creditCheck(validator: {order -> if (order.customer.creditScore == CreditScore.BAD && order.orderLines.size() > 1) { return "order.creditCheck.BAD" } }) …
  31. 31. Extended validation plugin• Constraint groups static constraints = { invoiceData { customer(cascade: true) contactPerson(nullable: false, blank: false) } …• Partial validation order.validate(groups: ["invoiceData"]) order.validate( includes: ["orderLines"], excludes: ["orderLines.deliveryAddress"])• getAllErrorsRecursive() dynamic method
  32. 32. Livesnippets• groovy & grails related documentation • Demo application • Code snippets • Directly linked to demo code• Github: https://github.com/houbie/livesnippets• Cloudfoundry: http://livesnippets.cloudfoundry.com
  33. 33. Questions ?

×