Monoidally construct web pages

root

Web-page

This package combines blaze-html, clay and jmacro into a framework-agnostic library to generate web pages dynamically from individual components. It is inspired by Yesod's widgets, but is more general, more powerful and can be used with all web frameworks.

Features

The Widget type is very expressive. Features include:

  • Sane algebraic interface.

  • Works with your web framework of choice, as long as it supports blaze-html, clay and jmacro (or a subset of them).

  • Rendering to multiple resources (e.g. separate stylesheet and script).

  • Fully embedded stylesheet and script languages (jmacro and clay), so you can write your web pages entirely in Haskell.

  • Page-specific and external stylesheets and script.

  • Type-safe routing.

  • Monoidal piece-by-piece construction of pages.

  • Flexible polymorphic body type. You can compose Pandoc or plain text pages and later convert (map/traverse) them to HTML.

  • Multi-part bodies a.k.a. sections: You can construct pages composed of individual sections, and each part of your program can contribute to the various sections individually. (This is really just a consequence of the previous and the first point in this list.)

  • Hierarchial titles.

  • Optional lens and MonadWriter interfaces. You can also combine them and use scribe and censoring to construct your pages from even richer or finer parts without writing your own combinators.

  • Optional page-unique identifier generation, if you use a monad for constructing widgets.

Usage

This is a brief overview of the process to construct and render a web page using this library. The overall process consists of the following steps:

  1. Widget: First construct a widget, which represents a web page component. In the sense of this library a full web page is a component, too.

  2. Page: Render the widget to a page. At this point you reduce the rich structure of the widget to a set of strings and functions.

  3. Resources: Realise the page by producing a set of resources (or files) that you can deliver to the client or load in a web browser.

The following sections explain each of these steps in detail. It is recommended to enable the OverloadedStrings extension to get rid of repetitive string conversions. The remainder of this tutorial assumes that it is enabled.

Widgets

Your web page usually consists of multiple components that you have to combine. A composition of individual components is itself a component. This idea is implemented by the Widget type:

Widget url h

Widget is a product of the various parts of a web page. It contains an optional title, head markup, a body, a script, a stylesheet and links to external resources.

The first argument url is the URL type. Typically you would want to use a custom type here that represents the valid routes of your web application (if you're writing one). However, type-safe routing is optional and not even handled directly by this library. Not to use it corresponds to setting url = Text, which is what we will do here:

type MyWidget = Widget Text

The second and most important argument h is the body type. The obvious choice is h = Html, but most of the power of this library comes from leaving it polymorphic, which we will use very soon.

Construction

Widget is a family of monoids. The easiest way to construct widgets is to construct them piece by piece using the Monoid interface. This library defines many functions to make this very convenient. To construct a body widget simply use the pure function:

pure :: h -> MyWidget h

Example:

import qualified Text.Blaze.Html5 as H
import Web.Page

widget1 :: MyWidget Html
widget1 =
    pure (H.h1 "A title and" <>
          H.p "a paragraph.")

As usual the type signatures are optional, but we will write them here for clarity.

While this library provides a powerful rendering mechanism, in some cases, especially when debugging or testing, you really just want to dump it on stdout using a quick and dirty one-liner:

hPutWidget
    :: ([Text] -> Tl.Text)
    -> Handle
    -> Widget Text Html
    -> IO ()

Most of this type signature is self-explanatory, except the first argument. It is the title renderer, which we will cover later. For now just use titleMinor " - ":

import System.IO

main :: IO ()
main = hPutWidget (titleMinor " - ") stdout widget1

The output of this program should look very similar to the following (here multi-lined and indented for readability):

<!DOCTYPE HTML>
<html>
    <head>
        <title></title>
        <meta charset="UTF-8">
    </head>
    <body>
        <h1>A title and</h1>
        <p>a paragraph.</p>
    </body>
</html>

Congratulations. You have constructed and rendered your first widget.

Composing widgets

As noted earlier you would typically construct your widgets piece by piece. Beside the pure function you will find the following functions handy for that:

caption :: (Monoid h) => Text  -> MyWidget h
script  :: (Monoid h) => JStat -> MyWidget h
style   :: (Monoid h) => Css   -> MyWidget h

The caption function constructs a title widget -- a widget that contains only a title. Using Monoid you can combine them to form a widget with a body and a title:

widget2 :: MyWidget Html
widget2 =
    caption "Test page" <>
    pure (H.h1 "Hello there") <>
    pure (H.p "I&#39;m a test page!")

Rendering this widget produces something like the following (most of the clutter removed for the sake of conciseness):

<head>
    <title>Test page</title>
</head>
<body>
    <h1>Hello there</h1>
    <p>I&#39;m a test page!</p>
</body>

Everything except the title can be composed, including the body, script and stylesheet. In particular pure is a monoid morphism:

pure x <> pure y = pure (x <> y)

As noted the only exception is the title, which uses last-wins composition:

caption x <> caption y = caption y

This is necessary for hierarchial titles to behave the way you would expect.

A widget can contain more than what has been explained above. See the documentation of the Web.Page.Widget.Core module for an overview.

Non-HTML bodies

The body of a widget does not have to be HTML. For example you can construct pure text widgets,

text1, text2 :: MyWidget Text
text1 = pure "just text"
text2 = pure "and some more text"

compose them (because Text is a monoid),

text3 :: MyWidget Text
text3 = text1 <> pure " " <> text2

or even just use OverloadedStrings,

text4 :: MyWidget Text
text4 =
    "just text " <>
    "and some more text"

which is actually a lot more general:

notQuiteText :: MyWidget Html
notQuiteText = "not quite" <> " text"

To render a widget the body must be of type Html. Our text widget is not, so we need to convert it first. As long as your conversion is just a function you can use the Functor instance of Widget (type simplified):

text             :: MyWidget Text
fmap toHtml      :: MyWidget Text -> MyWidget Html
fmap toHtml text ::                  MyWidget Html

If your conversion involves a functor you can use Traversable instead:

traverse
    :: (Applicative f)
    => (a -> f b)
    -> MyWidget a -> f (MyWidget b)

Widget templates

(A.k.a. the Monad instance that probably doesn't do what you thought it does.)

You can construct a widget with a hole that you can fill with markup elsewhere. The most common term for that concept is template. There are two ways to do it that differ in a subtlety. The first way is to write a widget function:

template :: Int -> String -> MyWidget Html

If the actual values come from widgets themselves,

intWidget :: MyWidget Int
strWidget :: MyWidget String

you would just use monadic composition:

do x <- intWidget
   y <- strWidget
   template x y

The template can derive anything from its arguments, including its title and its stylesheet. In some cases you might want to guarantee that a template derives only its body from its arguments, but still add a stylesheet that doesn't depend on the template arguments:

template2 :: MyWidget (Int -> String -> Html)

In this case you use applicative composition:

template2 <*> intWidget <*> strWidget

Widget composition is quite flexible. For example sometimes it's beneficial to separate the additional stylesheet from the actual body template:

template3 :: Int -> String -> Html
myStyle   :: MyWidget ()

And here is how you apply it:

template3 <$> (myStyle *> intWidget)
          <*> strWidget

Remember that ordering is relevant. If you want to add the styles of myStyle after the styles of intWidget and strWidget, compose differently:

template3 <$> intWidget
          <*> strWidget
          <*  myStyle

Multi-part widgets

A multi-part widget is really just a widget with a function as its body. Since functions with a monoid codomain form a monoid, we get most of the multi-part magic for free in Haskell.

This library does not specify what "part" means. A multi-part widget can denote anything from a page with multiple sections to a set of multiple pages. What it is depends on how you reduce such a widget to something that can be rendered.

Parts are indexed by a key, which can be anything with an Eq instance. You can index parts by integers or strings, or you can make a more domain-specific key type:

data Section = Header | Menu | Content | Footer
    deriving (Eq)

Now a widget can contribute to each section individually. You use the section function for that purpose:

section :: (Eq k, Monoid h) => k -> h -> MyWidget (k -> h)

The result of this function is a widget that has the given content only in the given section and with all other sections empty. To construct a widget that contributes to multiple sections, just use composition:

mpWidget1 :: MyWidget (Section -> Html)
mpWidget1 =
    section Menu (H.p "menu entries of widget1") <>
    section Footer (H.p "footer of widget1")

mpWidget2 :: MyWidget (Section -> Html)
mpWidget2 =
    section Menu (H.p "menu entries of widget2") <>
    section Content (H.p "content of widget2")

When you compose these two widgets, their sections are appended separately:

mpWidget :: MyWidget (Section -> Html)
mpWidget = mpWidget1 <> mpWidget2

To render the widget its body type must be Html, so we have to reduce it first. The way we reduce it gives concrete meaning to "multi-part". For multi-section pages you would use the flattenBody function:

flattenBody
    :: (Monoid h)
    => [k]
    -> MyWidget (k -> h)
    -> MyWidget h

This function takes a list of the sections you want to include in the resulting flattened widget, and the multi-section widget:

widget :: MyWidget Html
widget = flattenBody [Header, Menu, Content, Footer] mpWidget

Now you have a regular HTML widget that you can render. The body of the resulting document looks like this (comments added):

<!-- menu section -->
<p>menu entries of widget1</p>
<p>menu entries of widget2</p>

<!-- content section -->
<p>content of widget2</p>

<!-- footer section -->
<p>footer of widget1</p>

You can see that sections were composed separately.

Sometimes you have a lot of pages which are all pretty much the same, except for the body. You can represent such a set of pages as a multi-part widget:

manyPages :: MyWidget (PageIndex -> Html)

Now instead of flattening the widget you traverse it:

import Data.Traversable (sequenceA)

sequenceA :: MyWidget (k -> h) -> k -> MyWidget h

This pulls out the (k ->) to the front and gives you an indexed widget that you can render:

sequenceA manyPages :: PageIndex -> MyWidget h

You probably have noticed the similarity between multi-part widgets and templates. In particular applicative templates are just widgets with function-typed bodies, too. That makes sense, because technically there is no difference between them. Both are widgets with indexed bodies, but typically the index type is finite for a multi-part widget. You probably don't construct a page with infinitely many sections, because delivering it to a client would take a long time. =)

Hierarchial titles

On most multi-page sites it is very convenient to construct the title piece by piece as well hierarchially. The following two functions do exactly that:

caption :: (Monoid h) => Text -> MyWidget h
title   :: Text -> MyWidget h -> MyWidget h

We have already seen the caption function that allows you to give your widget a title:

caption "Page title" <>
pure (H.h1 "Heading")

Given a widget with a title the title function wraps it up in a higher-level title:

title "Site title" $
    caption "Page title" <>
    pure (H.h1 "Heading")

This is a widget with two title fragments in hierarchial order, the higher level title being "Site title" and the lower level title being "Page title". When composing multiple widgets that have a title, the last title wins. In particular in the following widget the title "subtitle1" is ignored:

title "title" $
    caption "subtitle1" <>
    title "subtitle2" (caption "subsubtitle")

In hierarchial order the resulting title will be:

"title" / "subtitle2" / "subsubtitle"

To understand why, decompose it first:

w1 = caption "subtitle1"
w2 = title "subtitle2" (caption "subsubtitle")

You can see that both w1 and w2 set a title. When you compose them (w1 <> w2), the title of the latter wins.

Not all widgets set a title, and applying title to a widget that doesn't set a title has no effect. Unless you manually apply the Widget constructor a widget only sets a title, if it uses caption or hasTitle:

hasTitle :: (Monoid h) => MyWidget h

The hasTitle function is useful in some hierarchial scenarios. It sets an empty title:

w = hasTitle <> pure (H.p "some body")

This widget does set a title, but an empty one (as opposed to a widget that sets no title at all). Now all title fragments are added via the title function:

title "title" $
    title "subtitle" $
        title "subsubtitle" w

When rendering a widget you need to provide a title renderer, which is a function of the following type (the type alias is not actually defined by this library):

import qualified Data.Text.Lazy as Tl

type TitleRenderer = [Text] -> Tl.Text

Such a function receives the individual title fragments in order from highest level to lowest level. It then constructs a title string, for example by intercalating a separator character between the fragments. Since intercalating is so common, there are two predefined renderers that do exactly that:

titleMajor :: Text -> TitleRenderer
titleMinor :: Text -> TitleRenderer

Examples:

titleMajor " / " ["site", "department", "page"] =
    "site / department / page"

titleMinor " - " ["site", "department", "page"] =
    "page - department - site"

In most cases you would want to use the titleMinor function, because it's usually more convenient for the user to have the page title in the leftmost position. Browser tab captions and title bars can quickly become too small to display the whole title, and the page title is usually much more informative than the site title.

Widget writers

Since widgets form a family of monoids you can also use a writer monad to construct them. This library defines a number of functions to make this very convenient. See the documentation of the Web.Page.Widget.Writer module for a full overview. Using a writer monad also plays well with identifier generation, if you need it.

In most cases the correct type will be inferred, but we will specify it regardless:

myWidget :: (MonadWriter (Widget url Html) m) => m ()

If you are like me, you prefer to write type signatures for your top-level definitions. A constraint alias is provided for your convenience. The following type signature is equivalent to the above:

myWidget :: (MonadWidget url Html m) => m ()

If writer is the only effect you need, there is an even simpler alias that you can use, which is equivalent to the above as well:

myWidget :: WidgetWriter url Html ()

Now we can construct the widget piece by piece:

myWidget = do
    setTitle "Hello"
    addBody (H.h1 "Hello")
    addBody (H.p "Hello world!")
    addStyle $ html ? do
        background white
        color black

You can build the widget by reducing the writer:

w :: Widget url Html
w = execWriter myWidget

This widget can now be rendered to a page.

Rendering to pages

So far we have only seen the hPutWidget function, which is very convenient for simple applications and static sites. However, it hides most of the power of the underlying renderer. This section explains how to use it.

To render a widget you can use the renderWidget function:

import qualified Data.Text.Lazy as Tl

renderWidget
    :: ([Text] -> Tl.Text)
    -> Widget Text Html
    -> Page

The first argument to this function is the title renderer as explained above. In most cases you would simply use the titleMinor function:

page :: Page
page = renderWidget (titleMinor " - ") w

Pages are an intermediate step between rendering and delivery. They are necessary, because this library allows you to render to multiple resources, for example to a markup document, a stylesheet and a script. You can then use a clever hash-based routing mechanism to tell clients to cache stylesheets and scripts forever and reduce the required bandwidth to a minimum.

Realisation

To process of generating from a page an actual set of resources that you can deliver is referred to as realisation. To simply render to a single document with an inline stylesheet and an inline script you can use the realiseInline function:

realiseInline :: Page -> Builder

All we need to do is to apply it to our page:

document :: Builder
document = realiseInline page

The Builder type is the one from blaze-builder. Most web frameworks use it for efficient bytestring concatenation and provide a simple interface to deliver those strings to clients. For example WAI provides the responseBuilder function. If you want to save the result to a file instead, just use toLazyByteString or toByteStringIO.