1

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?

glennsl
  • 28,186
  • 12
  • 57
  • 75
Yoann
  • 546
  • 4
  • 11
  • 1
    One thing that might help is to clone https://github.com/MangelMaxime/fulma-demo and spend some time browsing through it in VS Code + Ionide (or your preferred F# editor). It will take a little while to wrap your head around the application structure used in that demo, but once you understand it, you could do worse than to follow a similar structure in your own code. – rmunn Dec 07 '18 at 18:24
  • 1
    There's good explanation of scaling Elmish applications here: https://www.youtube.com/watch?v=-Oc4xJivY78 – DaveShaw Dec 07 '18 at 18:40
  • @rmunn thanks for the link , that might exactly be the thing I was looking for... :) – Yoann Dec 07 '18 at 19:12
  • @DaveShaw looks good, I'll watch it this week end. Thanks for the link. – Yoann Dec 07 '18 at 19:15

1 Answers1

1

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.

glennsl
  • 28,186
  • 12
  • 57
  • 75