HTML goodies

In this chapter we will explore several techniques that make development of the servers that serve HTML a bit more pleasant.

We will study how to:

  • use cookies
  • create type-safe stable URLs
  • use templates to render HTML-code

Cookies

A cookie is a special HTTP-header that asks the browser to save some information in local store so that it can be accessed in the sequence of requests. Often cookies are used to identify the logged in users and keep their sessions without the need to re-login. For great in-depth explanation of the cookies you can read this article.

We can ask the browser to set the cookie with a function:

setCookie :: (ToForm cookie, IsResp resp) => SetCookie cookie -> resp -> resp

We have a function to initialise basic cookies:

-- | Create a cookie from content with default settings
defCookie :: a -> SetCookie a

The auxiliary parameters let us specify expiration time and other useful parameters that control cookie life-cycle (see the type SetCookie for details). Note that the cookie should be an instance of ToForm class. It's easy to derive the instance with generics:

data MyCookie = MyCookie
  { token        :: Text
  , secretNumber :: Int
  } deriving (Generic, ToForm, FromForm)

We need FromForm instance to read the cookie.

How to read the cookies

The cookie is just an HTTP-Header with special name. To fetch the cookie on request we have a special input newtype-wrapper:

newtype Cookie = Cookie (Maybe a)

And we can use it just as any other input (like query parameter or capture) in the argument list of a handler function:

import Data.Text qualified as Text
...

showCookieHandler :: Cookie MyCookie -> Get IO (Resp Html Text)
showCookieHandler (Cookie mValue) = 
  pure $ ok $ case mValue of
    Just value -> cookieToText value
    Nothing -> "No cookie is set"
  where
    cookieToText = 
      Text.unwords 
        [ "The cookie is:"
        , fromString $ show (value.token, value.secretNumber)
        ]

Note that value of cookie is optional (wrapped in Maybe) as often on the first visit to page no cookie is set.

Type-safe stable URLs

In the previous chapter we have discussed HTML-example of prototype for a blog-post site. And while rendering page data to HTML we typed links to pages as text constants. For example:

    menu = do
      H.div $ do
        H.img H.! HA.src "/static/haskell-logo.png" H.! HA.alt "blog logo" H.! HA.width "100pt" H.! HA.style "margin-bottom: 15pt"
        H.ul H.! HA.style "list-style: none" $ do
          item "/index.html" "main page"
          item "/blog/read/post" "next post"
          item "/blog/read/quote" "next quote"
          item "/blog/write" "write new post"
          item "/blog/list" "list all posts"

    item ref name =
      H.li $ H.a H.! HA.href ref $ H.text name

This code is fragile because we can change the name of some path in the server definition and forget to update it in the View-functions.

To make it a bit more stable the safe URLs are introduced. Let's start explanation with most basic type Url:

-- | Url-template type.
data Url = Url
  { path :: Path
  -- ^ relative path
  , queries :: [(Text, Text)]
  -- ^ queries in the URL
  , captures :: Map Text Text
  -- ^ map of captures
  }

It encodes the typical URL. It has static part and two containers for query and capture parameters.

There is a class ToUrl that let us derive proper URL correspondence for a give server definition.

Let's explain it on hello-world server. It has two routes:

-- | The server definition
server :: Server IO
server = 
  "api/v1" /. 
    [ "hello" /. hello
    , "bye" /. bye
    ]

-- | The handler definition as a function
hello :: Get (Resp Text)

-- | The handler with a query parameter to ask for the user name
bye :: Query "user" Text -> Get (Resp Text)

This example is for JSON server but let's pretend that it is an HTML-server and we would like to generate URL's from server definition for our handlers. To do it first we will rewrite the definition a bit. We will place the handlers to a record and create similar record for URLs. This is not strictly necessary but it will make our code more structured. Also we will create type synonyms for handler's type-signatures:

type HelloRoute = Get (Resp Text)
type ByeRoute = Query "user" Text -> Get (Resp Text)

data Routes = Routes
  { hello :: HelloRoute
  , bye :: ByeRoute
  }

-- | The server definition
server :: Server IO
server routes = 
  "api/v1" /. 
    [ "hello" /. routes.hello
    , "bye" /. routes.bye
    ]

Let's define the URLs:

data Urls = Urls
  { hello :: UrlOf HelloRoute
  , bye :: UrlOf ByeRoute
  }

It resembles the handlers code only we use prefix UrlOf. This is a type-level function that knows which URL-creation function corresponds to handler.

For a static route with no arguments it will produce just constant Url. But for a route with arguments the result URL also is going to depend on those arguments in case that input is either Query, Optional, QueryFlag or Capture. All those inputs affect the look of the resulting URL.

For example for ByeRoute we get the type:

Query "user" Text -> Url

Let's link URLs to the server definition:

urls :: Urls
urls = Urls{..}
  where
    hello
      :| bye = toUrl (server undefined)

Here we use extension RecordWildCards to automatically assign proper fields by name from the where expression. Also one new thing is :|-operator. It is a suffix synonym for ordinary pair type:

data (:|) a b = a :| b

It let us bind to as many outputs as we like without the need for parens:

  where
    a :| b :| c :| d = toUrl (server undefined)

But we should be cautious to use so many routes as there are in the server definition. URL's are matched against the server definition in the same order as they appear in the server definition. In this way we get stable names for URL handles.

To use URL in the HTML we can use the function:

renderUrl :: (IsString a) => Url -> a

Which can convert it to any string-like type. It's compatible with blaze-html and we can use it as an argument for href attribute in the html link constructor.

We have to be careful on the order of URL's in the definition and make sure that they match with the order in the server. If inputs are incompatible a run-time error is produced on the call to the link.

The great part of it is that query or capture arguments are preserved in the URL constructors. And the proper corresponding URL text will be generated from arguments.

How to use HTML-templates

In the previous examples we wrote HTML view code with blaze-html DSL. The HTML construction is a Haskell function in this style. But often it is desirable to write HTML with the holes in it. So that holes can be substituted with values at run-time. Those files are called templates. Often templates are written by Web-designers.

In this section we will study how to use mustache templates with mig library. The mustache is a very simple and popular templating engine. For Haskell we have a great library stache that makes it easy to use mustache templates in the Haskell.

I recommend to read the tutorial on how to use the library.

Overview of the mustache

The main idea of the templates is very simple. The arguments are marked with double curly braces:

Hello {{name}}!
Nice to meet you in the {{place}}.

This template expects a JSON input to be completed with two fields:

{
  "name": "John",
  "place": "Garden"
}

Also we can render lists of things with special syntax:

Items:

{{#items}}
  * [name](ref) 
{{/items}}

It expects a JSON object:

{
  "items":
     [ { "name": "foo", "ref": "http://foo.com" }
     , { "name": "bar", "ref": "http://bar.com" }
     ]
}

As we can see it's format-agnostic and can work for any text. For HTML there are special marks that let us prevent escaping of HTML special symbols:

{{{content}}}

Triple curly braces mean that we do not need escaping of special symbols and HTML code is trusted and inlined directly. This type of input is often useful for the template for the main page where we define header, footer, menus and we have a single place for the main content of the page which is inlined as plain HTML. Without tripling of the braces w the HTML code will be rendered as text.

How to load templates

To load templates we can use the normal functions from the stache library. For this example we will inline them at compile-time in the code:

import Text.Mustache
import Text.Mustache.Compile.TH qualified as TH

mainTemplate :: Template
mainTemplate = $(TH.compileMustacheFile "HtmlTemplate/templates/main.html")

We need TemplateHaskell language extension activated for that. Also we include the directory with templates in our cabal file as extra-source-files:

extra-source-files:
    HtmlTemplate/templates/main.html
    HtmlTemplate/templates/post.html
    HtmlTemplate/templates/postNotFound.html

By the way all the code is taken from example in the mig repo called HtmlTemplate.

After we have loaded the templates we can apply them with JSON values. We get the data from the handler and convert it to HTML with templates. We will make a helper function for that:

import Text.Mustache
import Text.Blaze.Html.Renderer.Text qualified as H
import Text.Blaze.Html5 qualified as H
...

renderMustacheHtml :: (ToJSON a) => Template -> a -> Html
renderMustacheHtml template value =
  H.preEscapedLazyText $ renderMustache template (toJSON value)

It applies templates to JSON-like values. Let's look at the simple example:

-- Rendering of a single quote
instance ToMarkup Quote where
  toMarkup quote = renderMustacheHtml templates.quote quote

The template templates.quote is applied to value quote of the type:

-- | A quote
data Quote = Quote
  { content :: Text
  }
  deriving (Generic, ToJSON)

Note the deriving of ToJSON to make it convertible to JSON. Let's look at the template.quote. It is defined in the file HtmlTemplate/templates/quote.html:

<div> <h2> Quote of the day: </h2> </div>
<div> <p> {{content}} </p> </div>

So it has one argument content and exactly that is produced from Quote value as JSON.

Often we would like to render links in the HTML and they have the same structure with two arguments href and name. Also we can use stable URl's as links which we have just studied. For that the helper type was created:

data Link = Link
  { href :: Url
  , name :: Text
  }
  deriving (Generic, ToJSON)

It's convenient to use it with mustache templates. We can define template:

<a href="{{href}}">{{name}}</a> 

And apply the value of the Link type to it.

For example let's look at the template that lists all available blog-posts:

<div> 
  <h2> Posts: </h2>
  <ul>
    {{#posts}}
    <li>
      <a href="{{href}}"> {{name}} </a>
    </li>
    {{/posts}}
  </ul>
</div>

It expects a JSON object:

{
  "posts":
     [ { "href": "foo", "name": "foo" }
     , { "href": "bar", "name": "bar" }
     ]
}

And for that we have a Haskell type that matches this definition:

-- | List all posts
newtype ListPosts = ListPosts [BlogPostLink]

To render it we only need to add top level object with "post"-field.

-- | Rendering of all submited posts
instance ToMarkup ListPosts where
  toMarkup (ListPosts posts) = 
    renderMustacheHtml templates.listPosts $ toPostLinks posts

toPostLinks :: [BlogPostLink] -> Json.Value
toPostLinks posts =
  Json.object ["posts" Json..= fmap toLink posts]
  where
    toLink :: BlogPostLink -> Link
    toLink post =
      Link
        { href = urls.blogPost (Optional $ Just post.blogPostId)
        , name = post.title
        }

Note how we use the URL constructor as a function.

Other template engines

We are not limited with mustache for templates. The Haskell has many great templating libraries which also can be used like shakespeare or heist and many others.

I've chosen stache as it ports very widespread and simple solution mustache to Haskell. But other template engines can be used in the same way. The mig library is not tied to any of those libraries. Although I've tried stache and highly recommend it. It's easy to use and versatile.

Summary

We have studied several features that can make HTML-servers more easy to build.

  • We have discussed how to work with cookies
  • How to create stable type-safe URLs
  • How to use template engine stache (aka mustache) for our sites and make HTML-pages friendly for WEB-designers.

You can study the source code of the example HtmlTemplate in the mig repo to see how those concepts are used in action.