Using Lucid for HTML responses
Again you can run this “site” by typing:
stack exec site3
And again, I’ll take you through how it works, line by line!
Getting the basics out of the way:
> {-# LANGUAGE OverloadedStrings #-}
> import Web.Fn
> import Network.Wai (Response, Application)
> import Network.Wai.Handler.Warp (run)
> import Data.Monoid ((<>))
> import qualified Data.Text as T
> import Data.Text (Text)
> import Data.Text.Lazy (toStrict)
For this site, we’re going to add HTML with Lucid!
> import Lucid
Still need this text helper:
> tShow :: Show a => a -> Text
> tShow = T.pack . show
We’ll use the same Context:
> data Context = Context { req :: FnRequest }
> instance RequestContext Context where
> getRequest ctxt = req ctxt
> setRequest ctxt newRequest = ctxt { req = newRequest }
Because we still don’t need anything besides a request from a user.
But site
is going to be a bit different.
> site :: Context -> IO Response
> site ctxt = route ctxt [ end ==> indexHandler
> , path "add" // param "t1" // param "t2" ==> addNumbersHandler
> , path "add" // param "t1" // param "t2" ==> addWordsHandler
> , path "add" // end ==> addHandler
> ]
> `fallthrough` notFoundText "Page not found."
The routes have changed a little bit. Now we’re matching on parameters instead of path segments. (Had to move addHandler to bottom).
Our handlers are going to change because now we’re going to use Lucid to create HTML templates.
Here’s what building up a Lucid template might look like:
> indexView :: Html ()
> indexView = do
> html_ $ do
> head_ $ do
> title_ "My third Haskell site"
> body_ $ do
> h1_ "My third Haskell site"
> p_ "Try visiting \"add\"!"
You can put functions in a do block the same way you’d nest tags! This is pretty cool.
But look at the type! indexView
has the type Html ()
. Our handlers return Maybe Response
.
Fn provides okHtml
, a “200 OK” response for HTML, which expects Text
. So we have fill the gap between Lucid’s Html ()
and Text
.
> lucidHtml :: Html () -> IO (Maybe Response)
> lucidHtml h = okHtml $ toStrict $ renderText h
Lucid’s renderText
takes Html ()
and returns lazy text. Then we can use toStrict
to turn it into the Text
we want.
> indexHandler :: Context -> IO (Maybe Response)
> indexHandler ctxt = lucidHtml indexView
So beautiful!
Let’s use HTML to create a form for the addHandler
as well:
> addView :: Html ()
> addView = do
> html_ $ do
> body_ $ do
> form_ [action_ "add"] $ do
> label_ [for_ "t1"] "Thing 1:"
> input_ [id_ "t1", name_ "t1", type_ "text"]
> label_ [for_ "t2"] "Thing 2:"
> input_ [id_ "t2", name_ "t2", type_ "text"]
> input_ [type_ "submit"]
> addHandler :: Context -> IO (Maybe Response)
> addHandler ctxt = lucidHtml addView
Let’s inline some HTML into these handlers:
> addNumbersHandler :: Context -> Int -> Int -> IO (Maybe Response)
> addNumbersHandler ctxt firstNumber secondNumber =
> lucidHtml $ do
> p_ $ toHtml ("The sum of " <> tShow firstNumber <> " and " <> tShow secondNumber
> <> " is " <> tShow (firstNumber + secondNumber))
> addWordsHandler :: Context -> Text -> Text -> IO (Maybe Response)
> addWordsHandler ctxt firstWord secondWord =
> lucidHtml $ do
> p_ $ toHtml (firstWord <> " and " <> secondWord <> " added together is "
> <> (firstWord <> secondWord))
And run the site!
> main :: IO ()
> main = run 3000 waiApp
> waiApp :: Application
> waiApp = toWAI (Context defaultFnRequest) site