[ANN] Hyperbole 0.4 released: improved interface, more type safety, new features, examples and documentation

Hyperbole — the interactive serverside web framework inspired by HTMX, Elm, and Phoenix LiveView — has a new major release with many improvements:

Safer and Cleaner HyperViews

HyperViews have a cleaner interface via the class instance (Christian Georgii). Pages automatically handle any HyperViews. From https://docs.hyperbole.live/simple:

page :: (Hyperbole :> es) => Eff es (Page '[Message])
page = do
  pure $ col id $ do
    hyper Message1 $ messageView "Hello"
    hyper Message2 $ messageView "World!"

data Message = Message1 | Message2
  deriving (Show, Read, ViewId)

instance HyperView Message es where
  data Action Message = Louder Text
    deriving (Show, Read, ViewAction)

  update (Louder msg) = do
    let new = msg <> "!"
    pure $ messageView new

messageView :: Text -> View Message ()
messageView msg = do
  row id $ do
    button (Louder msg) id "Louder"
    el_ $ text msg

Live Examples and Documentation

Hackage documentation is greatly improved, with a step-by-step introduction explaining basics and best practices.

https://docs.hyperbole.live is now available with live examples, including links to source code. Notable additions include:

Other Improvements

Many thanks to the new contributors, and to everyone who submitted issues!

22 Likes

Interesting, I enjoyed reading through the examples. I had a similar, less developed version of this for a client back in 2020 that had the elm architecture (ish, it was a series of mealy machines with a bell on top) on the server, sending events via web socket to a React.render() as the vdom. It worked fairly well.

Question, how would you handle something like a details element, which can be expanded/collapsed on the browser? A naive interpretation of your framework is that if the view updates due to some other event, the details’ open/close status would be lost due to the vdom dropping the “open” attribute, thereby losing the user’s state.

2 Likes

Cool! Yeah there are so many different ways to implement something like this. After rewriting this multiple times, I’m really happy with the current version, and the tradeoffs it makes.

You can pass view state to actions. If you only had an open / closed state, it’s as simple as just having an Open and Close action, like in the transitions example: Hyperbole Examples

If you had some other unrelated action, you could make it a product type and add the current state to it as a selector.

So, this is contrived, but something like

data Action MyItem = Open | Close | SaveChanges Bool 

Where the Bool is the open state. Then write a view function that expects the open state

itemView :: Bool -> View MyItem ()

In update you can call itemView with the current state:

update Open = pure $ itemView True
update Close = pure $ itemView False
update (SaveChanges isOpen) = do
  someEffect
  pure $ itemView isOpen
1 Like

very nice design ! great work !
while may I know how this would handle recursive data type.

Data Ticket = FullPrice Float
            | DiscountBy Ticket Float

In HTML , can such type to be exposed to a form input based on Hyperbole ? Like ,user pick a drop down with option “Full Price” and “Discount”. if “Discount” is being selected, then a future HTML input form of will be shown up ?

Did you mean to allow for nested discounts? Your type would allow DiscountBy (DiscountBy (DiscountBy (FullPrice 100) 5) 5) 5. I think it might be better represented as:

type Price = Float
type Discount = Float
data Ticket = FullPrice Price | Discounted Discount Price

I don’t fully understand your question, but dropdowns are pretty flexible. You must enumerate the options, and specify the value for each. From https://docs.hyperbole.live/filter

familyDropdown :: Filters -> View Languages ()
familyDropdown filters =
  dropdown SetFamily (== filters.family) (border 1 . pad 10) $ do
    option Nothing "Any"
    option (Just ObjectOriented) "Object Oriented"
    option (Just Functional) "Functional"

The first parameter is (value -> Action id), and will be called with whichever option is selected. The second parameter is value -> Bool, and whichever option returns true will be selected by default.

If you wanted to show a different form if discount is selected, you could branch based on the currently selected value, displaying different views.

Does that answer your question?

Yes, the expression is expected to be nested ( can be N layers ) Thanks for the help, I’ll try to play a round it .