7.5.6 Subflows and Conversations 2.3.8

See the light - agile, industrial strength, rapid web application development made easy

(Quick Reference) # 7.5.6 Subflows and Conversations - Reference Documentation Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari Version: 2.3.8 ## 7.5.6 Subflows and Conversations #### Calling subflows Grails' Web Flow integration also supports subflows. A subflow is like a flow within a flow. For example take this search flow:
    def searchFlow = {
        displaySearchForm {
            on("submit").to "executeSearch"
        }
        executeSearch {
            action {
                [results:searchService.executeSearch(params.q)]
            }
            on("success").to "displayResults"
            on("error").to "displaySearchForm"
        }
        displayResults {
            on("searchDeeper").to "extendedSearch"
            on("searchAgain").to "displaySearchForm"
        }
        extendedSearch {
            // Extended search subflow
            subflow(controller: "searchExtensions", action: "extendedSearch")
            on("moreResults").to "displayMoreResults"
            on("noResults").to "displayNoMoreResults"
        }
        displayMoreResults()
        displayNoMoreResults()
    }
It references a subflow in the `extendedSearch` state. The controller parameter is optional if the subflow is defined in the same controller as the calling flow. Prior to 1.3.5, the previous subflow call would look like `subflow(new SearchExtensionsController().extendedSearchFlow)`, with the requirement that the name of the subflow state be the same as the called subflow (minus `Flow`). This way of calling a subflow is deprecated and only supported for backward compatibility. The subflow is another flow entirely:
    def extendedSearchFlow = {
        startExtendedSearch {
            on("findMore").to "searchMore"
            on("searchAgain").to "noResults"
        }
        searchMore {
            action {
               def results = searchService.deepSearch(ctx.conversation.query)
               if (!results) return error()
               conversation.extendedResults = results
            }
            on("success").to "moreResults"
            on("error").to "noResults"
        }
        moreResults()
        noResults()
    }
Notice how it places the `extendedResults` in conversation scope. This scope differs to flow scope as it lets you share state that spans the whole conversation, i.e. a flow execution including all subflows, not just the flow itself. Also notice that the end state (either `moreResults` or `noResults` of the subflow triggers the events in the main flow:
    extendedSearch {
        // Extended search subflow
        subflow(controller: "searchExtensions", action: "extendedSearch")
        on("moreResults").to "displayMoreResults"
        on("noResults").to "displayNoMoreResults"
    }
#### Subflow input and output Using conversation scope for passing input and output between flows can be compared with using global variables to pass information between methods. While this is OK in certain situations, it is usually better to use method arguments and return values. In webflow speak, this means defining input and output arguments for flows. Consider following flow for searching a person with a certain expertise:

    def searchFlow = {
            input {
                expertise(required: true)
                title("Search person")
            }        search {
                onEntry {
                    [personInstanceList: Person.findAllByExpertise(flow.expertise)]
                }
                on("select") {
                    flow.person = Person.get(params.id)
                }.to("selected")
                on("cancel").to("cancel")
            }        selected {
                output {
                    person {flow.person}
                }
            }
            cancel()
        }}
This flow accepts two input parameters: a required expertise argument an optional title argument with a default value All input arguments are stored in flow scope and are, just like local variables, only visible within this flow. A flow that contains required input will throw an exception when an execution is started without providing the input. The consequence is that these flows can only be started as subflows. Notice how an end state can define one or more named output values. If the value is a closure, this closure will be evaluated at the end of each flow execution. If the value is not a closure, the value will be a constant that is only calculated once at flow definition time. When a subflow is called, we can provide it a map with input values:

    def newProjectWizardFlow = {
        ...    managerSearch {
            subflow(controller: "person", action: "search",
                    input: [expertise : "management", title: "Search project manager"])
            on("selected") {
                flow.projectInstance.manager = currentEvent.attributes.person
            }.to "techleadSearch"
        }    techleadSearch {
            subflow(controller: "person", action: "search",
                    input: [expertise : { flow.technology }, title: "Search technical lead"])
            on("selected") {
                flow.projectInstance.techlead = currentEvent.attributes.person
            }.to "projectDetails"
        }    ...}
Notice again the difference between constant values like `expertise : "management"` and dynamic values like `expertise : { flow.technology }` The subflow output is available via `currentEvent.attributes` | Quick Reference (hide)