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 in the head, 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 our output. Widgets provide the glue that allows them to work together seamlessly.
{-# 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>
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 we want to write a component of a page for displaying the navbar. We want this to be “plug and play”: the function is called 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, widgets are interested in the following:
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. 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.
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.
You can actually override the default behavior and have the script and style code appear in a separate file. The scaffolded site provides this for you automatically.
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 typeclasses:
ToWidgetHead
and ToWidgetBody
. These work exactly as they seem they should. One example use case for this is to have fine-grained control over 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
3001
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 an HTML
value 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 content distribution network (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.
The whole idea of widgets is to increase composability. You can take individual pieces of HTML, CSS, and JavaScript, combine them 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:
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
If you’re so inclined, there’s also a Monoid
instance of Widget
,
meaning you can use mconcat
or a Writer
monad to build things up. In my
experience, it’s easiest and most natural to just use do
notation.
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
;
}
|
]
Let’s say we’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 prefer to keep your template in a
separate file.
The scaffolded site has an even more convenient function, widgetFile
,
which will also include your Lucius, Cassius, and Julius files automatically. We’ll cover that in Chapter 15.
You may have noticed that I’ve been avoiding type signatures so far. Why? 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 WidgetT site m a
. This data type is a
monad transformer. The last two arguments are the underlying monad and the
monadic value, respectively. The site
parameter is the specific foundation
type for your individual application. Because this type varies for each and every
site, it’s impossible for the libraries to define a single Widget
data type that 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 follows:
type
Widget
=
WidgetT
MyApp
IO
()
We set the monadic value to be ()
, as a widget’s value will ultimately be
thrown away. IO
is the standard base monad, and will be used in almost all
cases. The only exception is when writing a subsite. Subsites are a more
advanced topic and will be covered later, in Chapter 17.
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 HandlerT
and Handler
types.
It’s all well and good that we have these beautiful Widget
data types, 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 Widget -> Handler 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 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
But there’s still one thing that bothers me: that <style>
tag. There are a few
problems with it:
Unlike with Lucius or Cassius, it doesn’t get compile-time checked for correctness.
Granted, 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
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
.
This is a significant departure from how Yesod worked in versions 1.1 and
earlier. Previously, there was no MonadHandler
typeclass, and all functions
needed to be explicitly converted using lift
, not handlerToWidget
. The new
system is not only easier to use, but also avoids any strange monad transformer
tricks that were previously employed.
The basic building block of each page is the 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.