(Quick Reference)

7 Subflows and Conversations - Reference Documentation

Authors: Graeme Rocher

Version: 2.1.0

7 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