• Save
GR8Conf 2011: Grails Webflow
Upcoming SlideShare
Loading in...5
×
 

GR8Conf 2011: Grails Webflow

on

  • 4,917 views

 

Statistics

Views

Total Views
4,917
Views on SlideShare
4,907
Embed Views
10

Actions

Likes
3
Downloads
0
Comments
0

2 Embeds 10

http://universegroovygrails.blogspot.com 5
http://www.linkedin.com 5

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

GR8Conf 2011: Grails Webflow GR8Conf 2011: Grails Webflow Presentation Transcript

  • Grails webflow Ivo Houbrechts Ixor
  • 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
  • Agenda• Grails webflow plugin • Demo app • Spring webflow framework • Flow states • Subflows • Scopes • Ajax • Testing • Tips & pitfalls • Alternatives• Extended validation plugin• Questions
  • 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 webflow plugin • Subset of features
  • 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”) …
  • 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 /controllerName/flowName/viewstateId• Override with render(view: "myview")
  • 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") }
  • 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" } }
  • Action state: definitionsaveProject { action { if (flow.projectInstance.save()) { end() } else { projectInfo() } } on("end").to("end") on("projectInfo").to("projectInfo")}
  • 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") }
  • 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
  • Start and end states• Start state: first state in flow definition• End state • Empty cancel() • Redirect end { redirect(action: "show", id: flow.projectInstance.id) }
  • 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
  • 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"])
  • 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) } …
  • 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(…)
  • 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
  • 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
  • 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>
  • 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 …
  • Tip 1• Returning flow output to other controller actions • Use standard grails flash scope RequestContextHolder.currentRequestAttributes().flashScope.message = "Project was successfully created"
  • 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 = { … }
  • 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 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
  • 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 • 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
  • 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
  • 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" } }) …
  • 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
  • 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
  • Questions ?