Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More composable SubModelSeq #242

Closed
TysonMN opened this issue Jul 22, 2020 · 1 comment
Closed

More composable SubModelSeq #242

TysonMN opened this issue Jul 22, 2020 · 1 comment

Comments

@TysonMN
Copy link
Member

TysonMN commented Jul 22, 2020

I want the SubModelSeq sample to have more composable code. In particular, I want the counter bindings to be defined with the rest of the counter code and then for those bindings to be "mapped" to higher-level bindings at the App level.

I am trying to achieve this in this branch. Although I am trying to write really good code, I am not suggesting that necessarily merge in any of those commits as is. The primary purpose of this branch is as a proof of concept. I first want to get it working and then consider how to internally structure the code and what the external API should be.

Below, the function Bindings.mapMsgWithModel doesn't exist, but I have almost created it. To get this far, the two important changes are one important change is removing the wrapDispatch feature (which I originally requested, contributed, and later regretted) and changed the type of the binding argument OnCloseRequested from (essentially) 'msg to 'model -> 'msg. I am going to (independently) suggest this second change in a separate issue. (Edited: I did add issue #243 about changing the type of OnCloseRequested, but I realized that it is not important because it doesn't require a change to the public API.)

Tomorrow, I plan to continue trying to create Bindings.mapMsgWithModel. A remaining difficulty is the toMsg binding arguments. I currently don't expect to encounter any other difficulties.

Now to be more specific, I will compare what we have now to what I want. For what we have now, I use the code in PR #241.

Current Code

The counter code is...

type Counter =
  { Count: int
    StepSize: int }

type CounterMsg =
  | Increment
  | Decrement
  | SetStepSize of int
  | Reset

module Counter =

  let init =
    { Count = 0
      StepSize = 1 }
  
  let canReset = (<>) init
  
  let update msg m =
    match msg with
    | Increment -> { m with Count = m.Count + m.StepSize }
    | Decrement -> { m with Count = m.Count - m.StepSize }
    | SetStepSize x -> { m with StepSize = x }
    | Reset -> init

...which lacks bindings while the recursive bindings are

let rec counterBindings () : Binding<Model * Identifiable<Counter>, Msg> list = [
  "CounterIdText" |> Binding.oneWay(fun (_, c) -> c.Id)

  "CounterValue" |> Binding.oneWay(fun (_, c) -> c.Value.Count)
  "Increment" |> Binding.cmd(fun (_, c) -> CounterMsg (c.Id, Increment))
  "Decrement" |> Binding.cmd(fun (_, c) -> CounterMsg (c.Id, Decrement))
  "StepSize" |> Binding.twoWay(
    (fun (_, c) -> float c.Value.StepSize),
    (fun v (_, c) -> CounterMsg (c.Id, SetStepSize (int v))))
  "Reset" |> Binding.cmdIf(
    (fun (_, c) -> CounterMsg (c.Id, Reset)),
    (fun (_, c) -> Counter.canReset c.Value))

  "Remove" |> Binding.cmd(fun (_, c) -> Remove c.Id)

  "AddChild" |> Binding.cmd(fun (_, c) -> AddCounter c.Id)

  "MoveUp" |> Binding.cmdIf(
    (fun (_, c) -> MoveUp c.Id),
    (fun (m, c) -> m |> childrenCountersOfParentOf c.Id |> List.tryHead <> Some c))

  "MoveDown" |> Binding.cmdIf(
    (fun (_, c) -> MoveDown c.Id),
    (fun (m, c) -> m |> childrenCountersOfParentOf c.Id |> List.tryLast <> Some c))

  "GlobalState" |> Binding.oneWay(fun (m, _) -> m.SomeGlobalState)

  "ChildCounters" |> Binding.subModelSeq(
    (fun (m, c) -> m |> childrenCountersOf c.Id),
    (fun ((m, _), childCounter) -> (m, childCounter)),
    (fun (_, c) -> c.Id),
    snd,
    counterBindings)
]

Desired Code

Instead, I want the counter bindings to be defined with the rest of the Counter code like they are in the SingleCounter sample, which would look like...

type Counter =
  { Count: int
    StepSize: int }

type CounterMsg =
  | Increment
  | Decrement
  | SetStepSize of int
  | Reset

module Counter =

  let init =
    { Count = 0
      StepSize = 1 }
  
  let canReset = (<>) init
  
  let update msg m =
    match msg with
    | Increment -> { m with Count = m.Count + m.StepSize }
    | Decrement -> { m with Count = m.Count - m.StepSize }
    | SetStepSize x -> { m with StepSize = x }
    | Reset -> init

  let bindings () : Binding<Counter, CounterMsg> list = [
    "CounterValue" |> Binding.oneWay (fun m -> m.Count)
    "Increment" |> Binding.cmd Increment
    "Decrement" |> Binding.cmd Decrement
    "StepSize" |> Binding.twoWay(
      (fun m -> float m.StepSize),
      int >> SetStepSize)
    "Reset" |> Binding.cmdIf(Reset, canReset)
]

...and then the recursive bindings would be defined something like this

let rec counterBindings () : Binding<Model * Identifiable<Counter>, Msg> list =
  Counter.bindings
  |> Bindings.mapModel (fun (_, c) -> c.Value)
  |> Bindings.mapMsgWithModel (fun (_, c) msg -> CounterMsg (c.Id, msg))
  |> List.append
    [
      "CounterIdText" |> Binding.oneWay(fun (_, c) -> c.Id)
    
      "CounterValue" |> Binding.oneWay(fun (_, c) -> c.Value.Count)
      "Increment" |> Binding.cmd(fun (_, c) -> CounterMsg (c.Id, Increment))
      "Decrement" |> Binding.cmd(fun (_, c) -> CounterMsg (c.Id, Decrement))
      "StepSize" |> Binding.twoWay(
        (fun (_, c) -> float c.Value.StepSize),
        (fun v (_, c) -> CounterMsg (c.Id, SetStepSize (int v))))
      "Reset" |> Binding.cmdIf(
        (fun (_, c) -> CounterMsg (c.Id, Reset)),
        (fun (_, c) -> Counter.canReset c.Value))
    
      "Remove" |> Binding.cmd(fun (_, c) -> Remove c.Id)
    
      "AddChild" |> Binding.cmd(fun (_, c) -> AddCounter c.Id)
    
      "MoveUp" |> Binding.cmdIf(
        (fun (_, c) -> MoveUp c.Id),
        (fun (m, c) -> m |> childrenCountersOfParentOf c.Id |> List.tryHead <> Some c))
    
      "MoveDown" |> Binding.cmdIf(
        (fun (_, c) -> MoveDown c.Id),
        (fun (m, c) -> m |> childrenCountersOfParentOf c.Id |> List.tryLast <> Some c))
    
      "GlobalState" |> Binding.oneWay(fun (m, _) -> m.SomeGlobalState)
    
      "ChildCounters" |> Binding.subModelSeq(
        (fun (m, c) -> m |> childrenCountersOf c.Id),
        (fun ((m, _), childCounter) -> (m, childCounter)),
        (fun (_, c) -> c.Id),
        snd,
        counterBindings)
    ]
@TysonMN
Copy link
Member Author

TysonMN commented Jul 22, 2020

Closing this issue in favor of having the conversation in the draft PR #244

@TysonMN TysonMN closed this as completed Jul 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant