Search code examples
haskellfrpreactive-banana

Dynamically updating UI based on previous updates


I'm doing some research into practical aspects of FRP for UI's and I've been struggling with implementing the following functionality using reactive banana: based on the value of a selection box, a variable amount of list boxes are rendered which display some results. (I'm using WxHaskell.)

It was pretty straightforward to implement this using a bunch of prepared list boxes that are hidden and shown based on the result behavior, but this time I want it to create and destroy list boxes as needed, each list box linked to the results behavior.

So far I have the following ingredients:

  • an event eParam which is bound to the selection box
  • a behavior bResults :: Behavior t [[String]] defined with eParam (and stepper) which holds all the results (lists of items per list box)
  • an update function updateResultControls :: [SingleListBox ()] -> [[String]] -> IO [SingleListBox ()] which destroys or builds the list boxes based on the results. Note that the return type is in IO.

Looking at the BarTab example, I've tried to implement the following:

  • a behavior bResultControls :: Behavior t [SingleListBox ()] with the list boxes, defined as stepper [] eUpdateResultControls.
  • an event eUpdateResultControls :: Event t [SingleListBox ()] that performs the UI update. This event depends on the behaviors bResultControls and bResults. However, it also has to update the network and run IO, so I suspect Moment and execute will be involved. This is where I got stuck.

My latest attempt is this:

rec
  let
    bResultControls = stepper [] eResultControls
    bResultControlsUpdate = updateResultControls <$> bResultControls <*> bResults

  eResultControls <- execute $ FrameworksMoment . liftIO <$> (bResultControlsUpdate <@ eParam)

But I get the following type error:

Couldn't match type `m0 [SingleListBox ()]'
              with `forall t1. Frameworks t1 => Moment t1 [SingleListBox ()]'
Expected type: IO [SingleListBox ()]
               -> forall t. Frameworks t => Moment t [SingleListBox ()]
  Actual type: IO [SingleListBox ()] -> m0 [SingleListBox ()]
In the second argument of `(.)', namely `liftIO'
In the first argument of `(<$>)', namely
  `FrameworksMoment . liftIO'
In the second argument of `($)', namely
  `FrameworksMoment . liftIO <$> (bResultControlsUpdate <@ eParam)'

I suspect this will involve trimming some behaviors, or perhaps I'm going about this entirely the wrong way.


Solution

  • After some more reading and experimenting I got it to work with some careful trimming and refactoring (as hinted at by Heinrich):

    networkDescription :: forall t. Frameworks t => Moment t ()
    networkDescription = do
      eParam <- choiceSelection cParam
    
      let bResults = results <$> stepper x eParam
    
      bResults_ <- trimB bResults
    
      rec
        let
          bResultControls = stepper [] eResultControls
    
          mkResultControls :: [SingleListBox ()] -> [[String]] -> FrameworksMoment [SingleListBox ()]
          mkResultControls cs rs = FrameworksMoment $ do
            slResults <- liftIO $ updateResultControls cs rs
    
            bResults <- now bResults_
    
            sequence_ [sink sl [items :== (!! i) <$> bResults] | sl <- slResults | i <- [0..]]
    
            liftIO $ do
              let n = length rs
              set f [clientSize := sz (150 * n) 200]
              set pResults [layout := fill $ boxed "results" $ row n (map (fill . widget) slResults)]
              refit f
    
            return slResults
    
        eResultControls <- execute $ (mkResultControls <$> stepper [] eResultControls <*> bResults) <@ eParam
    
      return ()
    

    (Just got a little bug now where the event fires before the behavior updates but that should be easy to fix.)