Widgets

One of the challenges in web development is that we have to coordinate three different client-side technologies: HTML, CSS and JavaScript. Worse still, we have to place these components in different locations on the page: CSS in a style tag in the head, JavaScript in a script tag before the closing body tag, and HTML in the body. And never mind if you want to put your CSS and JavaScript in separate files!

In practice, this works out fairly nicely when building a single page, because we can separate our structure (HTML), style (CSS) and logic (JavaScript). But when we want to build modular pieces of code that can be easily composed, it can be a headache to coordinate all three pieces separately. Widgets are Yesod’s solution to the problem. They also help with the issue of including libraries, such as jQuery, one time only.

Our four template languages- Hamlet, Cassius, Lucius and Julius- provide the raw tools for constructing your output. Widgets provide the glue that allows them to work together seamlessly.

Synopsis

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
import           Yesod

data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App

getHomeR = defaultLayout $ do
    setTitle "My Page Title"
    toWidget [lucius| h1 { color: green; } |]
    addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"
    toWidget
        [julius|
            $(function() {
                $("h1").click(function(){
                    alert("You clicked on the heading!");
                });
            });
        |]
    toWidgetHead
        [hamlet|
            <meta name=keywords content="some sample keywords">
        |]
    toWidget
        [hamlet|
            <h1>Here's one way of including content
        |]
    [whamlet|<h2>Here's another |]
    toWidgetBody
        [julius|
            alert("This is included in the body itself");
        |]

main = warp 3000 App

This produces the following HTML (indentation added):

<!DOCTYPE html>
<html>
  <head>
    <title>My Page Title</title>
    <meta name="keywords" content="some sample keywords">
    <style>h1{color:green}</style>
  </head>
  <body>
    <h1>Here's one way of including content</h1>
    <h2>Here's another</h2>
    <script>
      alert("This is included in the body itself");
    </script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js">
    </script><script>
      $(function() {
        $('h1').click(function() {
          alert("You clicked on the heading!");
        });
      });
    </script>
  </body>
</html>

What’s in a Widget?

At a very superficial level, an HTML document is just a bunch of nested tags. This is the approach most HTML generation tools take: you define hierarchies of tags and are done with it. But let’s imagine that I want to write a component of a page for displaying the navbar. I want this to be "plug and play": I call the function at the right time, and the navbar is inserted at the correct point in the hierarchy.

This is where our superficial HTML generation breaks down. Our navbar likely consists of some CSS and JavaScript in addition to HTML. By the time we call the navbar function, we have already rendered the <head> tag, so it is too late to add a new <style> tag for our CSS declarations. Under normal strategies, we would need to break up our navbar function into three parts: HTML, CSS and JavaScript, and make sure that we always call all three pieces.

Widgets take a different approach. Instead of viewing an HTML document as a monolithic tree of tags, widgets see a number of distinct components in the page. In particular:

  • The title

  • External stylesheets

  • External Javascript

  • CSS declarations

  • JavaScript code

  • Arbitrary <head> content

  • Arbitrary <body> content

Different components have different semantics. For example, there can only be one title, but there can be multiple external scripts and stylesheets. However, those external scripts and stylesheets should only be included once. Arbitrary head and body content, on the other hand, has no limitation (someone may want to have five lorem ipsum blocks after all).

The job of a widget is to hold onto these disparate components and apply proper logic for combining different widgets together. This consists of things like taking the last title set and ignoring others, filtering duplicates from the list of external scripts and stylesheets, and concatenating head and body content.

Constructing Widgets

In order to use widgets, you’ll obviously need to be able to get your hands on them. The most common way will be via the ToWidget typeclass, and its toWidget method. This allows you to convert your Shakespearean templates directly to a Widget: Hamlet code will appear in the body, Julius scripts inside a <script>, and Cassius and Lucius in a <style> tag.

But what if you want to add some <meta> tags, which need to appear in the head? Or if you want some JavaScript to appear in the body instead of the head? For these purposes, Yesod provides two additional type classes: ToWidgetHead and ToWidgetBody. These work exactly as they seem they should. One example use case for this is to have fine-grained control of where your <script> tags end up getting inserted.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
import           Yesod

data App = App

mkYesod "App" [parseRoutes|
/      HomeR  GET
|]

instance Yesod App where

getHomeR :: Handler Html
getHomeR = defaultLayout $ do
    setTitle "toWidgetHead and toWidgetBody"
    toWidgetBody
        [hamlet|<script src=/included-in-body.js>|]
    toWidgetHead
        [hamlet|<script src=/included-in-head.js>|]

main :: IO ()
main = warp 3000 App

Note that even though toWidgetHead was called after toWidgetBody, the latter <script> tag appears first in the generated HTML.

In addition, there are a number of other functions for creating specific kinds of Widgets:

setTitle

Turns some HTML into the page title.

toWidgetMedia

Works the same as toWidget, but takes an additional parameter to indicate what kind of media this applies to. Useful for creating print stylesheets, for instance.

addStylesheet

Adds a reference, via a <link> tag, to an external stylesheet. Takes a type-safe URL.

addStylesheetRemote

Same as addStylesheet, but takes a normal URL. Useful for referring to files hosted on a CDN, like Google’s jQuery UI CSS files.

addScript

Adds a reference, via a <script> tag, to an external script. Takes a type-safe URL.

addScriptRemote

Same as addScript, but takes a normal URL. Useful for referring to files hosted on a CDN, like Google’s jQuery.

Combining Widgets

The whole idea of widgets is to increase composability. You can take these individual pieces of HTML, CSS and JavaScript, combine them together into something more complicated, and then combine these larger entities into complete pages. This all works naturally through the Monad instance of Widget, meaning you can use do-notation to compose pieces together.

myWidget1 = do
    toWidget [hamlet|<h1>My Title|]
    toWidget [lucius|h1 { color: green } |]

myWidget2 = do
    setTitle "My Page Title"
    addScriptRemote "http://www.example.com/script.js"

myWidget = do
    myWidget1
    myWidget2

-- or, if you want
myWidget' = myWidget1 >> myWidget2

Generate IDs

If we’re really going for true code reuse here, we’re eventually going to run into name conflicts. Let’s say that there are two helper libraries that both use the class name “foo” to affect styling. We want to avoid such a possibility. Therefore, we have the newIdent function. This function automatically generates a word that is unique for this handler.

getRootR = defaultLayout $ do
    headerClass <- newIdent
    toWidget [hamlet|<h1 .#{headerClass}>My Header|]
    toWidget [lucius| .#{headerClass} { color: green; } |]

whamlet

Let’s say you’ve got a fairly standard Hamlet template, that embeds another Hamlet template to represent the footer:

page =
    [hamlet|
        <p>This is my page. I hope you enjoyed it.
        ^{footer}
    |]

footer =
    [hamlet|
        <footer>
            <p>That's all folks!
    |]

That works fine if the footer is plain old HTML, but what if we want to add some style? Well, we can easily spice up the footer by turning it into a Widget:

footer = do
    toWidget
        [lucius|
            footer {
                font-weight: bold;
                text-align: center
            }
        |]
    toWidget
        [hamlet|
            <footer>
                <p>That's all folks!
        |]

But now we’ve got a problem: a Hamlet template can only embed another Hamlet template; it knows nothing about a Widget. This is where whamlet comes in. It takes exactly the same syntax as normal Hamlet, and variable (#{…}) and URL (@{…}) interpolation are unchanged. But embedding (^{…}) takes a Widget, and the final result is a Widget. To use it, we can just do:

page =
    [whamlet|
        <p>This is my page. I hope you enjoyed it.
        ^{footer}
    |]

There is also whamletFile, if you would prefer to keep your template in a separate file.

Types

You may have noticed that I’ve been avoiding type signatures so far. The simple answer is that each widget is a value of type Widget. But if you look through the Yesod libraries, you’ll find no definition of the Widget type. What gives?

Yesod defines a very similar type: data WidgetFor site a. This data type is a monad transformer. The last argument a is the monadic value. The site parameter is the specific foundation type for your individual application. Since this type varies for each and every site, it’s impossible for the libraries to define a single Widget datatype which would work for every application.

Instead, the mkYesod Template Haskell function generates this type synonym for you. Assuming your foundation data type is called MyApp, your Widget synonym is defined as:

type Widget = WidgetFor MyApp ()

We set the monadic value to be (), since a widget’s value will ultimately be thrown away.

Once we know about our Widget type synonym, it’s easy to add signatures to our previous code samples:

footer :: Widget
footer = do
    toWidget
        [lucius|
            footer {
                font-weight: bold;
                text-align: center
            }
        |]
    toWidget
        [hamlet|
            <footer>
                <p>That's all folks!
        |]

page :: Widget
page =
    [whamlet|
        <p>This is my page. I hope you enjoyed it.
        ^{footer}
    |]

When we start digging into handler functions some more, we’ll encounter a similar situation with the HandlerFor and Handler types.

Using Widgets

It’s all well and good that we have these beautiful Widget datatypes, but how exactly do we turn them into something the user can interact with? The most commonly used function is defaultLayout, which essentially has the type signature WidgetFor → HandlerFor Html.

defaultLayout is actually a typeclass method, which can be overridden for each application. This is how Yesod apps are themed. So we’re still left with the question: when we’re inside defaultLayout, how do we unwrap a Widget? The answer is widgetToPageContent. Let’s look at some (simplified) types:

data PageContent url = PageContent
    { pageTitle :: Html
    , pageHead :: HtmlUrl url
    , pageBody :: HtmlUrl url
    }
widgetToPageContent :: Widget -> Handler (PageContent url)

This is getting closer to what we need. We now have direct access to the HTML making up the head and body, as well as the title. At this point, we can use Hamlet to combine them all together into a single document, along with our site layout, and we use withUrlRenderer to convert that Hamlet result into actual HTML that’s ready to be shown to the user. The next example demonstrates this process.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
import           Yesod

data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]

myLayout :: Widget -> Handler Html
myLayout widget = do
    pc <- widgetToPageContent widget
    withUrlRenderer
        [hamlet|
            $doctype 5
            <html>
                <head>
                    <title>#{pageTitle pc}
                    <meta charset=utf-8>
                    <style>body { font-family: verdana }
                    ^{pageHead pc}
                <body>
                    <article>
                        ^{pageBody pc}
        |]

instance Yesod App where
    defaultLayout = myLayout

getHomeR :: Handler Html
getHomeR = defaultLayout
    [whamlet|
        <p>Hello World!
    |]

main :: IO ()
main = warp 3000 App

There’s still one thing that bothers me: that style tag. There are a few problems with it:

  • Unlike Lucius or Cassius, it doesn’t get compile-time checked for correctness.

  • Granted that the current example is very simple, but in something more complicated we could get into character escaping issues.

  • We’ll now have two style tags instead of one: the one produced by myLayout, and the one generated in the pageHead based on the styles set in the widget.

We have one more trick in our bag to address this: we apply some last-minute adjustments to the widget itself before calling widgetToPageContent. It’s actually very easy to do: we just use do-notation again.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
import           Yesod

data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]

myLayout :: Widget -> Handler Html
myLayout widget = do
    pc <- widgetToPageContent $ do
        widget
        toWidget [lucius| body { font-family: verdana } |]
    withUrlRenderer
        [hamlet|
            $doctype 5
            <html>
                <head>
                    <title>#{pageTitle pc}
                    <meta charset=utf-8>
                    ^{pageHead pc}
                <body>
                    <article>
                        ^{pageBody pc}
        |]

instance Yesod App where
    defaultLayout = myLayout

getHomeR :: Handler Html
getHomeR = defaultLayout
    [whamlet|
        <p>Hello World!
    |]

main :: IO ()
main = warp 3000 App

Using handler functions

We haven’t covered too much of the handler functionality yet, but once we do, the question arises: how do we use those functions in a widget? For example, what if your widget needs to look up a query string parameter using lookupGetParam?

The first answer is the function handlerToWidget, which can convert a Handler action into a Widget answer. However, in many cases, this won’t be necessary. Consider the type signature of lookupGetParam:

lookupGetParam :: MonadHandler m => Text -> m (Maybe Text)

This function will live in any instance of MonadHandler. And conveniently, Widget is also a MonadHandler instance. This means that most code can be run in either Handler or Widget. And if you need to explicitly convert from Handler to Widget, you can always use handlerToWidget.

Summary

The basic building block of each page is a widget. Individual snippets of HTML, CSS, and JavaScript can be turned into widgets via the polymorphic toWidget function. Using do-notation, you can combine these individual widgets into larger widgets, eventually containing all the content of your page.

Unwrapping these widgets is usually performed within the defaultLayout function, which can be used to apply a unified look-and-feel to all your pages.

Chapters