Search code examples
f#fable-f#elm-architectureelmish

dispatch method in view for parent child composition


I am trying to understand the way fable is supposed to work with parent child composition. Things are quite easy when it comes to update method, init, and the definition of commands. But the view method and its dispatch method are tricky to find out

In my code, the child is:

module DeploymentView

type DeploymentTypeView =
    | DeployContainerView

type Model = { 
 CurrentView : DeploymentTypeView option
}

type Msg =
| ShowDeployContainer

let init () : Model =
    let initialModel = { 
        CurrentView = None
    }
    initialModel


let update (msg : Msg) (currentModel : Model) : Model * Cmd<Msg> = 
    match msg with 
    | ShowDeployContainer ->
        let nextModel = { 
            currentModel with CurrentView = Some DeployContainerView 
        }
        nextModel, Cmd.none     
    | _ -> currentModel, Cmd.none


let view (model : Model) (dispatch : Msg -> unit) =
    [
        Content.content [ Content.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Left) ] ]
            [ 
                Heading.h3 [] [ str ("Deployments: ") ] 
            ] 

        Columns.columns []
            [ 
                Column.column [] [ button "deploy container" (fun _ -> dispatch ShowDeployContainer) ]
            ] 
    ]

And following this documentation about parent child processing I have define a parent like this one:

module Client

type PortalView =
| DeploymentView of DeploymentView.Model
| ProductAdministrationView


type Model = { 
    CurrentPortal : PortalView option
}

// The Msg type defines what events/actions can occur while the application is running
// the state of the application changes *only* in reaction to these events
type Msg =
| ShowDeployment
| ShowAdministration
| DeployContainerView of DeploymentView.Msg


// defines the initial state and initial command (= side-effect) of the application
let init () : Model * Cmd<Msg> =
    let initialModel = { 
        CurrentPortal = None 
    }
    initialModel, Cmd.none

let update (msg : Msg) (currentModel : Model) : Model * Cmd<Msg> =
    match  msg with
    | ShowDeployment ->
        let nextModel = { 
            currentModel with CurrentPortal = Some <| DeploymentView(DeploymentView.init())
        }
        nextModel, Cmd.none
    | ShowAdministration ->
        let nextModel = { 
            currentModel with CurrentPortal = Some ProductAdministrationView
        }
        nextModel, Cmd.none
    | DeployContainerView msg' ->
        let res, cmd = 
            match currentModel.CurrentPortal with
            | Some(DeploymentView(m)) -> DeploymentView.update msg' m
            | _ -> DeploymentView.init(), Cmd.none
        { currentModel with CurrentPortal = Some(DeploymentView(res)) }, Cmd.map DeployContainerView cmd

So far so good, my issue comes when it goes to the rendering of the view itself. The client view uses a function as follows:

let view (model : Model) (dispatch : Msg -> unit) 

where Msg is of type DeploymentView.Msg whereas in the parent view I have access to a dispatch of type Client.Msg -> unit. how can I decompose the parent dispatch to map it to the child dispatch signature?


Solution

  • You can very easily create a dispatch function that conforms to what the child expects by using the >> operator:

    DeploymentView.view deploymentViewModel (DeployContainerView >> dispatch)
    

    which is equivalent to doing:

    DeploymentView.view deploymentViewModel (fun msg -> msg |> DeployContainerView |> dispatch)
    

    That is, it wraps the child's message in DeployContainerView, then passes that to dispatch.

    On another note, it is a common and good convention to use a Msg suffix on constructors used to wrap child msg types. You may want to consider renaming DeployContainerView to DeploymentContainerMsg.