seanhess / hyperbole

Haskell interactive serverside web framework inspired by HTMX
Other
93 stars 6 forks source link

Client-side triggers. Resolves #15 #44

Open seanhess opened 2 weeks ago

seanhess commented 2 weeks ago

This PR adds support for triggers. Resolves #15

If you know that another hyperview exists, you can trigger

controls :: (Hyperbole :> es) => Controls -> ControlsAction -> Eff es (View Controls ())
controls _ (Broadcast t) = do
  trigger (Message 0) (Say t)
  trigger (Message 1) (Say t)
  trigger (Message 2) (Say t)
  pure viewControls

TODO

seanhess commented 2 weeks ago

I can't figure out how to make this type-safe: you can attempt to trigger any HyperView, even if it isn't handled or doesn't exist:

controls :: (Hyperbole :> es) => Controls -> ControlsAction -> Eff es (View Controls ())
controls _ (Broadcast t) = do
  trigger (Message 0) (Say t)
  trigger (Message 1) (Say t)
  trigger (Message 2) (Say t)
  trigger NotHandledView OhNo
  pure viewControls

And all we get is a lousy javascript console error :(

To fix, we would need to keep the hyperview's type in the effect somehow, because trigger doesn't know which view's handler you are resolving.

trigger :: (Hyperbole :> es, HyperView id) => id -> Action id -> Eff es ()

If we adopt #43, we can automatically handle it, but there's no way to know the view exists.

I'm unsure if this is a good idea. I know people are going to use/abuse it if included, and perhaps there's always a better way to handle this. I guess target has the same problem. You're guessing that the other view exists, but we can at least enforce that it is handled.

One option would be to make this something that goes in the view instead,

seanhess commented 6 days ago

With the class-based handlers in #43, there's a (Reader id : es) in the handler type. That means we should be able to make triggers type-safe. Update to come next week

seanhess commented 2 days ago

@cgeorgii I have this working, but I'm on the fence about whether to include it or not. I can't think of a good motivating example that you couldn't do more simply by wrapping everything in a big parent view. If we include it, I think people will use it too often.

Take a look at Example.Triggers. It works!

instance Handle Controls es where
  handle (Broadcast t) = do
    trigger (Message 0) (Say t)
    trigger (Message 1) (Say t)
    trigger (Message 2) (Say t)

    pure viewControls

Now compare it to Example.Triggers.Nested, which wraps everything in a big parent view. Instead of having a Controls view, we have a big main view called Content with the same actions

instance Handle Content es where
  handle (Broadcast t) = do
    pure $ viewContent (Just t)

viewContent :: Maybe Text -> View Content ()
viewContent broadcast = do
  let msg = fromMaybe "ready" broadcast
  row (gap 10) $ do
    viewControls
    hyper (Message 0) $ viewMessage msg
    hyper (Message 1) $ viewMessage msg
    hyper (Message 2) $ viewMessage msg  

Can you think of a reason why we need triggers that can't be solved with a big parent view?

cgeorgii commented 16 hours ago

Hey @seanhess! Thanks for pinging me. I've been swamped lately but I'll try to take a look and let you know what I think on Friday, alright? :)

seanhess commented 14 hours ago

That would be excellent, thank you!