{- | Module: Web.Hyperbole Copyright: (c) 2024 Sean Hess License: BSD3 Maintainer: Sean Hess Stability: experimental Portability: portable Create fully interactive HTML applications with type-safe serverside Haskell. Inspired by [HTMX](https://htmx.org/), [Elm](https://elm-lang.org/), and [Phoenix LiveView](https://www.phoenixframework.org/) -} module Web.Hyperbole ( -- * Introduction -- $use -- ** Hello World -- $hello -- ** Interactivity -- $interactive -- ** Independent Updates -- $multi -- ** Examples -- $examples -- * Run an Application liveApp , Warp.run , page , basicDocument -- ** Type-Safe Routes , routeRequest -- maybe belongs in an application section , Route , routeUrl , route -- * Pages -- ** Page , Page , load , handle -- ** HyperView , HyperView (..) , hyper -- * Interactive Elements -- ** Buttons , button -- ** Dropdowns , dropdown , option , Option -- ** Events , onRequest , onLoad , DelayMs -- * Type-Safe Forms -- | Painless forms with type-checked field names, and support for validation. See [Example.Forms](https://github.com/seanhess/hyperbole/blob/main/example/Example/Forms.hs) , FormField -- ** Form View , form , field , label , input , submit , placeholder , InputType (..) -- ** Handlers , formField -- ** Validation , Validation (..) , validate , validation , invalidText -- * Hyperbole Effect , Hyperbole -- ** Request Info , reqParam , reqParams , request , lookupParam , formData -- ** Response , notFound , redirect , respondEarly -- ** Sessions , session , setSession , clearSession -- * Advanced , target , view , Param (..) , Response -- * Exports -- ** Web.View -- | Hyperbole is tightly integrated with [Web.View](https://hackage.haskell.org/package/web-view/docs/Web-View.html) for HTML generation , module Web.Hyperbole.View -- ** Embeds -- | Embedded CSS and Javascript to include in your document function. See 'basicDocument' , module Web.Hyperbole.Embed -- ** Effectful -- $effects , module Effectful -- ** Other , Application , Generic ) where import Effectful (Eff, (:>)) import GHC.Generics (Generic) import Network.Wai (Application) import Network.Wai.Handler.Warp as Warp (run) import Web.Hyperbole.Application import Web.Hyperbole.Effect import Web.Hyperbole.Embed import Web.Hyperbole.Forms (FormField, InputType (..), Validation (..), field, form, formField, input, invalidText, label, placeholder, submit, validate, validation) import Web.Hyperbole.HyperView import Web.Hyperbole.Route import Web.Hyperbole.View -- import Web.View.Types.Url (Segment) {- $use Single Page Applications (SPAs) require the programmer to write two programs: a Javascript client and a Server, which both must conform to a common API Hyperbole allows us instead to write a single Haskell program which runs exclusively on the server. All user interactions are sent to the server for processing, and a sub-section of the page is updated with the resulting HTML. There are frameworks that support this in different ways, including [HTMX](https://htmx.org/), [Phoenix LiveView](https://www.phoenixframework.org/), and others. Hyperbole has the following advantages 1. 100% Haskell 2. Type safe views, actions, routes, and forms 3. Elegant interface with little boilerplate 4. VirtualDOM updates over sockets, fallback to HTTP 5. Easy to use Like [HTMX](https://htmx.org/), Hyperbole extends the capability of UI elements, but it uses Haskell's type-system to prevent common errors and provide default functionality. Specifically, a page has multiple update targets called 'HyperView's. These are automatically targeted by any UI element that triggers an action inside them. You can only trigger existing actions that match that HyperView Like [Phoenix LiveView](https://www.phoenixframework.org/), it upgrades the page to a WebSocket connection and uses VirtualDOM for live updates Like [Elm](https://elm-lang.org/), it relies on an update function to 'handle' actions, but greatly simplifies the Elm Architecture by handling state with extensible effects. 'form's are easy to use with minimal boilerplate Depends heavily on the following frameworks * [Effectful](https://hackage.haskell.org/package/effectful-core) * [Web View](https://hackage.haskell.org/package/web-view) -} {- $hello Hyperbole applications run via [Warp](https://hackage.haskell.org/package/warp) and [WAI](https://hackage.haskell.org/package/wai) They are divided into top-level 'Page's. We use 'load' to handle the initial page load @ main = do 'run' 3000 $ do 'liveApp' ('basicDocument' \"Example\") ('page' messagePage) messagePage = do 'load' $ do pure $ do 'el' 'bold' "Message Page" messageView "Hello World" messageView m = do el_ "Message:" el_ (text m) @ -} {- $interactive Embed 'HyperView's to add type-safe interactivity to subsections of a 'Page'. To do this, first we connect a view id type to the actions it supports @ data Message = Message deriving (Generic, 'Param') data MessageAction = Louder Text deriving (Generic, 'Param') instance 'HyperView' Message where type Action Message = MessageAction @ Next we add a 'handle'r for our view type. It performs side effects, and returns a new view of the same type @ message :: Message -> MessageAction -> Eff es (View Message ()) message _ (Louder m) = do -- side effects let new = m <> "!" pure $ messageView new @ We update our parent page view to make the messageView interactive using 'hyper', and add our 'handle'r to the 'Page' @ messagePage = do 'handle' message 'load' $ do pure $ do 'el' 'bold' "Message Page" 'hyper' Message $ messageView "Hello World" @ Finally, let's add a 'button' to our view. When clicked, Hyperbole will run the `message` handler, and update our view, leaving the page header untouched @ messageView :: Text -> 'View' Message () messageView m = do 'el_' m 'button' (Louder m) id "Change Message" @ -} {- $multi Multiple views update independently, as long as they have different values for their View Id. Add an Int identifier to Message @ data Message = Message Int deriving (Generic, 'Param') @ We can embed multiple HyperViews on the same page with different ids. Each button will update its view independently @ messagePage = do 'handle' message 'load' $ do pure $ do 'el' bold "Message Page" 'hyper' (Message 1) $ messageView \"Hello\" 'hyper' (Message 2) $ messageView \"World\" @ -} {- $examples The [example directory](https://github.com/seanhess/hyperbole/blob/main/example/example/README.md) contains an app with pages demonstrating different features * [Main](https://github.com/seanhess/hyperbole/blob/main/example/Main.hs) * [Simple](https://github.com/seanhess/hyperbole/blob/main/example/Example/Simple.hs) * [Counter](https://github.com/seanhess/hyperbole/blob/main/example/Example/Counter.hs) * [CSS Transitions](https://github.com/seanhess/hyperbole/blob/main/example/Example/Transitions.hs) * [Forms](https://github.com/seanhess/hyperbole/blob/main/example/Example/Forms.hs) * [Sessions](https://github.com/seanhess/hyperbole/blob/main/example/Example/Forms.hs) * [Redirects](https://github.com/seanhess/hyperbole/blob/main/example/Example/Redirects.hs) * [Lazy Loading and Polling](https://github.com/seanhess/hyperbole/blob/main/example/Example/LazyLoading.hs) * [Errors](https://github.com/seanhess/hyperbole/blob/main/example/Example/Errors.hs) * [Contacts (Advanced)](https://github.com/seanhess/hyperbole/blob/main/example/Example/Contacts.hs) -} {- $effects Hyperbole is tighly integrated with [Effectful](https://hackage.haskell.org/package/effectful) for extensible effects. It is used to implement the 'Hyperbole' and 'Server' effects. * See [Effectful.Dispatch.Dynamic](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html) for an example of how to create a custom effect * See [Example.Counter](https://github.com/seanhess/hyperbole/blob/main/example/Example/Counter.hs) for an example of how to compose an existing effect -}