stefan-hoeck / idris2-dom-mvc

Single Page Web Applications in Idris
BSD 3-Clause "New" or "Revised" License
18 stars 2 forks source link

Composable widgets? #32

Closed gergoerdi closed 1 year ago

gergoerdi commented 1 year ago

Just a draft of an idea for orthogonal UI elements:

module UI.Widget

import Web.MVC
import Data.Either

record Widget where
  constructor MkWidget
  0 St : Type
  0 Ev : Type
  init : St
  update : Ev -> St -> St
  display : Maybe Ev -> St -> Cmd Ev

Semigroup Widget where
  w1 <+> w2 = MkWidget
    { St = (w1.St, w2.St)
    , Ev = Either w1.Ev w2.Ev
    , init = (w1.init, w2.init)
    , update = \ev, (s1, s2) => case ev of
        Left  ev1 => (w1.update ev1 s1, s2)
        Right ev2 => (s1, w2.update ev2 s2)
    , display = \ev, (s1, s2) => batch
        [ Left  <$> w1.display (getLeft =<< ev)  s1
        , Right <$> w2.display (getRight =<< ev) s2
        ]
    }

Monoid Widget where
  neutral = MkWidget
    { St = ()
    , Ev = Void
    , init = ()
    , update = absurd
    , display = \_, _ => neutral
    }

runWidget : (JSErr -> IO ()) -> Widget -> IO ()
runWidget onError w = runMVC update display onError Nothing w.init
  where
    update : Maybe w.Ev -> w.St -> w.St
    update Nothing = id
    update (Just ev) = w.update ev

    display : Maybe w.Ev -> w.St -> Cmd (Maybe w.Ev)
    display ev = map Just . w.display ev
stefan-hoeck commented 1 year ago

Nice find! Feel free to open a PR with a new module Web.MVC.Widget for adding this. I'm sure there are other patterns still hidden from us. In another issue, comonads were mentioned as an abstract for user interfaces. Might well be that what you wrote above is related.

gergoerdi commented 1 year ago

Design question: should we have display : Maybe Ev -> St -> Cmd Ev, or setup : Cmd Ev, display : Ev -> St -> Cmd Ev? The difference is that with the latter we can change the Semigroup operator so that events firing from widgets don't cause updates on the other widgets.

stefan-hoeck commented 1 year ago

Design question: should we have display : Maybe Ev -> St -> Cmd Ev, or setup : Cmd Ev, display : Ev -> St -> Cmd Ev? The difference is that with the latter we can change the Semigroup operator so that events firing from widgets don't cause updates on the other widgets.

I'd prefer the latter here. So we start with an initial state and an initial Cmd Ev instead of an initializing event. That sounds like a good choice.

gergoerdi commented 1 year ago

I'm finding in practice that setup : St -> Cmd Ev is such a huge ergonomic win over setup : Cmd Ev that it should be worth it even if it's technically / theoretically redundant.

stefan-hoeck commented 1 year ago

Closed via #34