Chapter 4. Shakespearean Templates
Yesod uses the Shakespearean family of template languages as its standard approach to HTML, CSS, and JavaScript creation. This language family shares some common syntax, as well as a few overarching principles:
-
As little interference to the underlying language as possible, while providing conveniences where unobtrusive
-
Compile-time guarantees on well-formed content
-
Static type safety, greatly helping the prevention of XSS (cross-site scripting) attacks
-
Automatic validation of interpolated links, whenever possible, through type-safe URLs
There is nothing inherently tying Yesod to these languages, or the other way around: each can be used independently of the other. This chapter will address these template languages on their own, while the remainder of the book will use them to enhance Yesod application development.
Synopsis
There are four main languages at play: Hamlet is an HTML templating language, Julius is for JavaScript, and Cassius and Lucius are both for CSS. Hamlet and Cassius are both whitespace-sensitive formats, using indentation to denote nesting. By contrast, Lucius is a superset of CSS, keeping CSS’s braces for denoting nesting. Julius is a simple passthrough language for producing JavaScript; the only added feature is variable interpolation.
Note
Cassius is, in fact, just an alternative syntax for Lucius. They both use the same processing engine underneath, but Cassius files have indentation converted into braces before processing. The choice between the two is purely one of syntactical preference.
Hamlet (HTML)
$doctype 5<html>
<head>
<title>
#{pageTitle} - My Site<link
rel=
stylesheet
href=
@{Stylesheet}
>
<body>
<h1
.
page-title
>
#{pageTitle}<p>
Here is a list of your friends: $if null friends<p>
Sorry, I lied, you don't have any friends. $else<ul>
$forall Friend name age<-
friends
<
li
>
#{name} (#{age} years old)<footer>
^{copyright}
Lucius (CSS)
section
.blog
{
padding
:
1em
;
border
:
1px
solid
#000
;
h1
{
color
:
#{
headingColor
}
;
background-image
:
url
(@
{
MyBackgroundR
}
);
}
}
Julius (JavaScript)
$
(
function
(){
$
(
"section.#{sectionClass}"
).
hide
();
$
(
"#mybutton"
).
click
(
function
(){
document
.
location
=
"@{SomeRouteR}"
;});
^
{
addBling
}
});
Types
Before we jump into syntax, let’s take a look at the various types involved. We mentioned in the introduction that types help protect us from XSS attacks. For example, let’s say that we have an HTML template that should display someone’s name. It might look like this:
<p>
Hello, my name is #{name}
What should happen to name
, and what should its data type be? A naive approach
would be to use a Text
value, and insert it verbatim. But that would give us
quite a problem when name
is equal to something like:
<script src='http://nefarious.com/evil.js'></script>
What we want is to be able to entity-encode the name, so that <
becomes <
.
An equally naive approach is to simply entity-encode every piece of text that
gets embedded. What happens when you have some preexisting HTML generated from
another process? For example, on the Yesod website, all Haskell code snippets
are run through a colorizing function that wraps up words in appropriate span
tags. If we entity-escaped everything, code snippets would be completely
unreadable!
Instead, we have an Html
data type. In order to generate an Html
value, we
have two options for APIs. The ToMarkup
typeclass provides a way to convert
String
and Text
values into Html
via its toHtml
function,
automatically escaping entities along the way. This would be the approach we’d
want for name
. For the code snippet example, we would use the
preEscapedToMarkup
function.
When you use variable interpolation in Hamlet (the HTML Shakespeare language),
it automatically applies a toHtml
call to the value inside. So, if you
interpolate a String
, it will be entity-escaped, but if you provide an Html
value, it will appear unmodified. In the code snippet example, we might
interpolate with something like #{preEscapedToMarkup myHaskellHtml}
.
Note
The Html
data type and the functions mentioned are all provided
by the blaze-html
package. This allows Hamlet to interact with all other
blaze-html
packages, and lets Hamlet provide a general solution for producing
blaze-html
values. Also, we get to take advantage of blaze-html
’s amazing
performance.
Similarly, we have Css
/ToCss
, as well as Javascript
/ToJavascript
. These
provide some compile-time sanity checks to ensure we haven’t accidentally stuck some
HTML in our CSS.
Note
One other advantage on the CSS side is some helper data types for colors and units. For example:
.red { color: #{colorRed} }
Refer to the Haddock documentation for more details.
Type-Safe URLs
Possibly the most unique feature in Yesod is type-safe URLs, and the ability to
use them conveniently is provided directly by Shakespeare. Usage is nearly
identical to variable interpolation; we just use the at sign (@
) instead of the
hash (#
). We’ll cover the syntax later, but first let’s clarify the intuition.
Suppose we have an application with two routes: http://example.com/profile/home is the homepage, and http://example.com/display/time displays the current time. If we want to link from the homepage to the time, there are three different ways of constructing the URL:
-
As a relative link (e.g., ../display/time)
-
As an absolute link, without a domain (e.g., /display/time)
-
As an absolute link, with a domain (e.g., http://example.com/display/time)
But there are problems with each approach. The first will break if either URL changes. Also, it’s not suitable for all use cases; RSS and Atom feeds, for instance, require absolute URLs. The second is more resilient to change than the first, but still won’t be acceptable for RSS and Atom. And while the third works fine for all use cases, you’ll need to update every single URL in your application whenever your domain name changes. You think that doesn’t happen often? Just wait till you move from using a development server to a staging server and finally into production.
But more importantly, there is one huge issue with all three approaches: if you change your routes at all, the compiler won’t warn you about the broken links. Not to mention that typos can wreak havoc as well.
The goal of type-safe URLs is to let the compiler check things for us as much as possible. In order to facilitate this, our first step must be to move away from plain old text, which the compiler doesn’t understand, to some well-defined data types. For our simple application, let’s model our routes with a sum type:
data
MyRoute
=
Home
|
Time
Instead of placing a link like /display/time in our template, we can use the
Time
constructor. But at the end of the day, HTML is made up of text, not
data types, so we need some way to convert these values to text. We call this a
URL rendering function—here’s a simple example:
renderMyRoute
::
MyRoute
->
Text
renderMyRoute
Home
=
"http://example.com/profile/home"
renderMyRoute
Time
=
"http://example.com/display/time"
Note
URL rendering functions are actually a bit more complicated than this. They need to address query string parameters, handle records within the constructor, and more intelligently handle the domain name. But in practice, you don’t need to worry about this, because Yesod will automatically create your render functions. The one thing to point out is that the type signature is actually a little more complicated to handle query strings:
type
Query
=
[(
Text
,
Text
)]
type
Render
url
=
url
->
Query
->
Text
renderMyRoute
::
Render
MyRoute
renderMyRoute
Home
_
=
...
renderMyRoute
Time
_
=
...
OK, we have our render function, and we have type-safe URLs embedded in the
templates. How exactly does this fit together? Instead of generating an Html
(or Css
or Javascript
) value directly, Shakespearean templates actually
produce a function, which takes the render function and produces HTML. Let’s take a quick peek to see how Hamlet would work under the
surface. Supposing we had a template:
<a href=@{Time}>The time
this would translate roughly into the Haskell code:
\
render
->
mconcat
[
"<a href='"
,
render
Time
,
"'>The time</a>"
]
Syntax
All Shakespearean languages share the same interpolation syntax and are able to utilize type-safe URLs. They differ in the syntax specific for their target language (HTML, CSS, or JavaScript). Let’s explore each language in turn.
Hamlet Syntax
Hamlet is the most sophisticated of the languages. Not only does it provide
syntax for generating HTML, but it also allows for basic control structures:
conditionals, looping, and maybe
s.
Tags
Obviously, tags will play an important part in any HTML template language. In Hamlet, we try to stick very close to existing HTML syntax to make the language more comfortable. However, instead of using closing tags to denote nesting, we use indentation. So, something like this in HTML:
<body>
<p>
Some paragraph.</p>
<ul>
<li>
Item 1</li>
<li>
Item 2</li>
</ul>
</body>
would be:
<body> <p>Some paragraph. <ul> <li>Item 1 <li>Item 2
In general, we find this to be easier to follow than HTML once you get accustomed to it. The only tricky part arises when dealing with whitespace before and after tags. For example, let’s say we want to create the following HTML:
<p>
Paragraph<i>
italic</i>
end.</p>
We want to make sure that whitespace is preserved after the word “Paragraph” and before the word “end.” To do so, we use two simple escape characters:
<p> Paragraph # <i>italic \ end.
The whitespace escape rules are actually quite simple:
-
If the first non-space character in a line is a backslash, the backslash is ignored. (Note: this will also cause any tag on this line to be treated as plain text.)
-
If the last character in a line is a hash, it is ignored.
One other thing: Hamlet does not escape entities within its content. This is done on purpose to allow existing HTML to be more easily copied in. So, the preceding example could also be written as:
<p>Paragraph <i>italic</i> end.
Notice that the first tag will be automatically closed by Hamlet, while the
inner <i>
tag will not. You are free to use whichever approach you want; there
is no penalty for either choice. Be aware, however, that the only time you
use closing tags in Hamlet is for such inline tags; normal tags are not closed.
Another outcome of this is that any tags after the first tag do not have special treatment for IDs and classes. For example, the following Hamlet snippet:
<p #firstid>Paragraph <i #secondid>italic end.
generates the HTML:
<p
id=
"firstid"
>
Paragraph<i
#
secondid
>
italic</i>
end.</p>
Notice how the <p>
tag is automatically closed, and its attributes get special
treatment, whereas the <i>
tag is treated as plain text.
Interpolation
What we have so far is nice, simplified HTML, but it doesn’t let us interact with our Haskell code at all. How do we pass in variables? The answer is simple—by using interpolation:
<head> <title>#{title}
The hash followed by a pair of braces denotes variable interpolation. In this case, the title
variable from the scope in which the template was
called will be used. Let me state that again: Hamlet automatically has access
to the variables in scope when it’s called. There is no need to specifically
pass variables in.
You can apply functions within an interpolation. You can use string and numeric
literals in an interpolation. You can also use qualified modules. Both parentheses
and the dollar sign can be used to group statements together. And at the end,
the toHtml
function is applied to the result, meaning any instance of
ToHtml
can be interpolated. Take, for instance, the following code:
-- Just ignore the QuasiQuote stuff for now, and that shamlet thing.
-- It will be explained later.
{-# LANGUAGE QuasiQuotes #-}
import
Text.Hamlet
(
shamlet
)
import
Text.Blaze.Html.Renderer.String
(
renderHtml
)
import
Data.Char
(
toLower
)
import
Data.List
(
sort
)
data
Person
=
Person
{
name
::
String
,
age
::
Int
}
main
::
IO
()
main
=
putStrLn
$
renderHtml
[
shamlet
|
<
p
>
Hello
,
my
name
is
#
{
name
person
}
and
I
am
#
{
show
$
age
person
}
.
<
p
>
Let's
do
some
funny
stuff
with
my
name
:
#
<
b
>#
{
sort
$
map
toLower
(
name
person
)}
<
p
>
Oh
,
and
in
5
years
I'll
be
#
{
show
((
+
)
5
(
age
person
))}
years
old
.
|
]
where
person
=
Person
"Michael"
26
What about our much-touted type-safe URLs? They are almost identical to
variable interpolation in every way, except they start with an at sign (@
)
instead. In addition, there is embedding via a caret (^
), which allows you to
embed another template of the same type. The next code sample demonstrates both
of these:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import
Text.Hamlet
(
HtmlUrl
,
hamlet
)
import
Text.Blaze.Html.Renderer.String
(
renderHtml
)
import
Data.Text
(
Text
)
data
MyRoute
=
Home
render
::
MyRoute
->
[(
Text
,
Text
)]
->
Text
render
Home
_
=
"/home"
footer
::
HtmlUrl
MyRoute
footer
=
[
hamlet
|
<
footer
>
Return
to
#
<
a
href
=@
{
Home
}
>
Homepage
.
|
]
main
::
IO
()
main
=
putStrLn
$
renderHtml
$
[
hamlet
|
<
body
>
<
p
>
This
is
my
page
.
^
{
footer
}
|
]
render
Additionally, there is a variant of URL interpolation that allows you to embed
query string parameters. This can be useful, for example, for creating
paginated responses. Instead of using @{…}
, you add a question mark
(@?{…}
) to indicate the presence of a query string. The value you provide
must be a two-tuple with the first value being a type-safe URL and the second
being a list of query string parameter pairs. The following code snippet shows an example:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import
Text.Hamlet
(
HtmlUrl
,
hamlet
)
import
Text.Blaze.Html.Renderer.String
(
renderHtml
)
import
Data.Text
(
Text
,
append
,
pack
)
import
Control.Arrow
(
second
)
import
Network.HTTP.Types
(
renderQueryText
)
import
Data.Text.Encoding
(
decodeUtf8
)
import
Blaze.ByteString.Builder
(
toByteString
)
data
MyRoute
=
SomePage
render
::
MyRoute
->
[(
Text
,
Text
)]
->
Text
render
SomePage
params
=
"/home"
`
append
`
decodeUtf8
(
toByteString
$
renderQueryText
True
(
map
(
second
Just
)
params
))
main
::
IO
()
main
=
do
let
currPage
=
2
::
Int
putStrLn
$
renderHtml
$
[
hamlet
|
<
p
>
You
are
currently
on
page
#
{
currPage
}
.
<
a
href
=@?
{(
SomePage
,
[(
"page"
,
pack
$
show
$
currPage
-
1
)])}
>
Previous
<
a
href
=@?
{(
SomePage
,
[(
"page"
,
pack
$
show
$
currPage
+
1
)])}
>
Next
|
]
render
This generates the expected HTML:
<p>
You are currently on page 2.<a
href=
"/home?page=1"
>
Previous</a>
<a
href=
"/home?page=3"
>
Next</a>
</p>
Attributes
In the preceding example, we put an href
attribute on the <a>
tag. Let’s elaborate on the syntax:
-
You can have interpolations within the attribute value.
-
The equal sign and value for an attribute are optional, just like in HTML. So,
<input type=checkbox checked>
is perfectly valid. -
There are two convenience attributes: for
id
, you can use the hash, and for classes, the period (in other words,<p #paragraphid .class1 .class2>
). -
Although quotes around the attribute value are optional, they are required if you want to embed spaces.
-
You can add an attribute optionally by using colons. To make a checkbox only checked if the variable
isChecked
isTrue
, you would write<input type=checkbox :isChecked:checked>
. To have a paragraph be optionally red, you could use<p :isRed:style="color:red">
. (This also works for class names—for example,<p :isCurrent:.current>
will set the classcurrent
toTrue
.)
Conditionals
Eventually, you’ll want to put some logic in your page. The goal of Hamlet
is to make the logic as minimalistic as possible, pushing the heavy lifting
into Haskell. As such, our logical statements are very basic… so basic, that
it’s if
, elseif
, and else
:
$if isAdmin <p>Welcome to the admin section. $elseif isLoggedIn <p>You are not the administrator. $else <p>I don't know who you are. Please log in so I can decide if you get access.
All the same rules of normal interpolation apply to the content of the conditionals.
maybe
Similarly, we have a special construct for dealing with maybe
values. These
could technically be dealt with using if
, isJust
, and fromJust
, but this
is more convenient and avoids partial functions:
$maybe name <- maybeName <p>Your name is #{name} $nothing <p>I don't know your name.
In addition to simple identifiers, you can use a few other, more complicated values on the lefthand side, such as constructors and tuples:
$maybe Person firstName lastName <- maybePerson <p>Your name is #{firstName} #{lastName}
The righthand side follows the same rules as interpolations and allows variables, function application, and so on.
case
Pattern matching is one of the great strengths of Haskell. Sum types allow you to
cleanly model many real-world types, and case
statements let you safely
match, enabling the compiler to warn you if a case was missed. Hamlet gives you the
same power:
$case foo $of Left bar <p>It was left: #{bar} $of Right baz <p>It was right: #{baz}
Lucius Syntax
Lucius is one of two CSS templating languages in the Shakespeare family. It is intended to be a superset of CSS, leveraging the existing syntax while adding in a few more features. Here are some key points:
-
Like Hamlet, it allows both variable and URL interpolation.
-
CSS blocks are allowed to nest.
-
You can declare variables in your templates.
-
A set of CSS properties can be created as a mixin and reused in multiple declarations.
Starting with the second point, let’s say you want to have special styling
for some tags within your article
. In plain ol’ CSS, you’d have to write:
article
code
{
background-color
:
grey
;
}
article
p
{
text-indent
:
2em
;
}
article
a
{
text-decoration
:
none
;
}
In this case, there aren’t that many clauses, but having to type out article
each time is still a bit of a nuisance. Imagine if you had a dozen or so of these—not the worst thing in the world, but a bit of an annoyance. Lucius helps you out here:
article { code { background-color: grey; } p { text-indent: 2em; } a { text-decoration: none; } > h1 { color: green; } }
Having Lucius variables allows you to avoid repeating yourself. A simple example would be to define a commonly used color:
@textcolor: #ccc; /* just because we hate our users */ body { color: #{textcolor} } a:link, a:visited { color: #{textcolor} }
Mixins are a relatively new addition to Lucius. The idea is to declare a mixin
providing a collection of properties, and then embed that mixin in a template
using caret interpolation (^
). The following example demonstrates how we
could use a mixin to deal with vendor prefixes:
{-# LANGUAGE QuasiQuotes #-}
import
Text.Lucius
import
qualified
Data.Text.Lazy.IO
as
TLIO
-- Dummy render function.
render
=
undefined
-- Our mixin, which provides a number of vendor prefixes for transitions.
transition
val
=
[
luciusMixin
|
-
webkit
-
transition
:
#
{
val
};
-
moz
-
transition
:
#
{
val
};
-
ms
-
transition
:
#
{
val
};
-
o
-
transition
:
#
{
val
};
transition
:
#
{
val
};
|
]
-- Our actual Lucius template, which uses the mixin.
myCSS
=
[
lucius
|
.
some
-
class
{
^
{
transition
"all 4s ease"
}
}
|
]
main
=
TLIO
.
putStrLn
$
renderCss
$
myCSS
render
Cassius Syntax
Cassius is a whitespace-sensitive alternative to Lucius. As mentioned in the synopsis, it uses the same processing engine as Lucius but preprocesses all input to insert braces to enclose subblocks and semicolons to terminate lines. This means you can leverage all features of Lucius when writing Cassius. Here’s a simple example:
#banner border: 1px solid #{bannerColor} background-image: url(@{BannerImageR})
Calling Shakespeare
The question, of course, arises at some point: how do I actually use this stuff? There are three different ways to call out to Shakespeare from your Haskell code:
- QuasiQuotes
-
QuasiQuotes allow you to embed arbitrary content within your Haskell that is converted into Haskell code at compile time.
- External file
-
In this case, the template code is in a separate file that is referenced via Template Haskell.
- Reload mode
-
Both of the preceding modes require a full recompile to see any changes. In reload mode, your template is kept in a separate file and referenced via Template Haskell. But at runtime, the external file is reparsed from scratch each time.
Note
Reload mode is not available for Hamlet but is for Cassius, Lucius, and Julius. There are too many sophisticated features in Hamlet that rely directly on the Haskell compiler and could not feasibly be reimplemented at runtime.
One of the first two approaches should be used in production. They both embed the entirety of the template in the final executable, simplifying deployment and increasing performance. The advantage of the QuasiQuotes approach is the simplicity: everything stays in a single file. For short templates, this can be a very good fit. However, in general, the external file approach is recommended because:
-
It follows nicely in the tradition of separating logic from presentation.
-
You can easily switch between external file and debug mode with some simple C preprocessor macros, meaning you can keep development rapid and still achieve high performance in production.
Because special quasiquoters and Template Haskell functions are involved, you need to be sure to enable the appropriate language extensions and use correct syntax. You can see a simple example of each approach in the following code snippets:
{-# LANGUAGE OverloadedStrings #-}
-- we're using Text below
{-# LANGUAGE QuasiQuotes #-}
import
Text.Hamlet
(
HtmlUrl
,
hamlet
)
import
Data.Text
(
Text
)
import
Text.Blaze.Html.Renderer.String
(
renderHtml
)
data
MyRoute
=
Home
|
Time
|
Stylesheet
render
::
MyRoute
->
[(
Text
,
Text
)]
->
Text
render
Home
_
=
"/home"
render
Time
_
=
"/time"
render
Stylesheet
_
=
"/style.css"
template
::
Text
->
HtmlUrl
MyRoute
template
title
=
[
hamlet
|
$
doctype
5
<
html
>
<
head
>
<
title
>#
{
title
}
<
link
rel
=
stylesheet
href
=@
{
Stylesheet
}
>
<
body
>
<
h1
>#
{
title
}
|
]
main
::
IO
()
main
=
putStrLn
$
renderHtml
$
template
"My Title"
render
{-# LANGUAGE OverloadedStrings #-}
-- we're using Text below
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-}
-- to control production versus debug
import
Text.Lucius
(
CssUrl
,
luciusFile
,
luciusFileDebug
,
renderCss
)
import
Data.Text
(
Text
)
import
qualified
Data.Text.Lazy.IO
as
TLIO
data
MyRoute
=
Home
|
Time
|
Stylesheet
render
::
MyRoute
->
[(
Text
,
Text
)]
->
Text
render
Home
_
=
"/home"
render
Time
_
=
"/time"
render
Stylesheet
_
=
"/style.css"
template
::
CssUrl
MyRoute
#
if
PRODUCTION
template
=
$
(
luciusFile
"template.lucius"
)
#
else
template
=
$
(
luciusFileDebug
"template.lucius"
)
#
endif
main
::
IO
()
main
=
TLIO
.
putStrLn
$
renderCss
$
template
render
-- @template.lucius foo { bar: baz }
The naming scheme for the functions is very consistent:
Language | Quasiquoter | External file | Reload |
---|---|---|---|
Hamlet |
|
|
N/A |
Cassius |
|
|
|
Lucius |
|
|
|
Julius |
|
|
|
Alternative Hamlet Types
So far, we’ve seen how to generate an HtmlUrl
value from Hamlet, which is a
piece of HTML with embedded type-safe URLs. There are currently three other
values we can generate using Hamlet: plain HTML, HTML with URLs, and
internationalized messages/widgets. That last one will be covered in more
detail in Chapter 5.
To generate plain HTML without any embedded URLs, we use “simplified Hamlet.” There are a few changes:
-
We use a different set of functions, prefixed with an “s”. So, the quasiquoter is
shamlet
and the external file function isshamletFile
. How we pronounce those is still up for debate. -
No URL interpolation is allowed. Doing so will result in a compile-time error.
-
Embedding (the caret interpolator) no longer allows arbitrary
HtmlUrl
values. The rule is that the embedded value must have the same type as the template itself, so in this case it must beHtml
. That means that forshamlet
, embedding can be completely replaced with normal variable interpolation (with a hash).
Dealing with internationalization (i18n) in Hamlet is a bit complicated. Hamlet supports i18n via a message data type, very similar in concept and implementation to a type-safe URL. As an example, let’s say we want to create an application that says “hello” and indicates how many apples you’ve eaten. Those messages can be represented with a data type:
data
Msg
=
Hello
|
Apples
Int
Next, we need to convert that into something human readable, so we define some render functions:
renderEnglish
::
Msg
->
Text
renderEnglish
Hello
=
"Hello"
renderEnglish
(
Apples
0
)
=
"You did not buy any apples."
renderEnglish
(
Apples
1
)
=
"You bought 1 apple."
renderEnglish
(
Apples
i
)
=
T
.
concat
[
"You bought "
,
T
.
pack
$
show
i
,
" apples."
]
Now we want to interpolate those Msg
values directly in the template. For that, we use underscore interpolation:
$doctype 5 <html> <head> <title>i18n <body> <h1>_{Hello} <p>_{Apples count}
This kind of a template now needs some way to turn those values into HTML. So, just like with type-safe URLs, we pass in a render function. To represent this, we define a new type synonym:
type
Render
url
=
url
->
[(
Text
,
Text
)]
->
Text
type
Translate
msg
=
msg
->
Html
type
HtmlUrlI18n
msg
url
=
Translate
msg
->
Render
url
->
Html
At this point, you can pass renderEnglish
, renderSpanish
, or
renderKlingon
to this template, and it will generate nicely translated output
(depending, of course, on the quality of your translators). Here’s the complete
program:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import
Data.Text
(
Text
)
import
qualified
Data.Text
as
T
import
Text.Hamlet
(
HtmlUrlI18n
,
ihamlet
)
import
Text.Blaze.Html
(
toHtml
)
import
Text.Blaze.Html.Renderer.String
(
renderHtml
)
data
MyRoute
=
Home
|
Time
|
Stylesheet
renderUrl
::
MyRoute
->
[(
Text
,
Text
)]
->
Text
renderUrl
Home
_
=
"/home"
renderUrl
Time
_
=
"/time"
renderUrl
Stylesheet
_
=
"/style.css"
data
Msg
=
Hello
|
Apples
Int
renderEnglish
::
Msg
->
Text
renderEnglish
Hello
=
"Hello"
renderEnglish
(
Apples
0
)
=
"You did not buy any apples."
renderEnglish
(
Apples
1
)
=
"You bought 1 apple."
renderEnglish
(
Apples
i
)
=
T
.
concat
[
"You bought "
,
T
.
pack
$
show
i
,
" apples."
]
template
::
Int
->
HtmlUrlI18n
Msg
MyRoute
template
count
=
[
ihamlet
|
$
doctype
5
<
html
>
<
head
>
<
title
>
i18n
<
body
>
<
h1
>
_
{
Hello
}
<
p
>
_
{
Apples
count
}
|
]
main
::
IO
()
main
=
putStrLn
$
renderHtml
$
(
template
5
)
(
toHtml
.
renderEnglish
)
renderUrl
Other Shakespeare
In addition to HTML, CSS, and JavaScript helpers, there is also some more
general-purpose Shakespeare available. shakespeare-text
provides a simple way
to create interpolated strings, much like people are accustomed to in scripting
languages like Ruby and Python. This package’s utility is definitely not
limited to Yesod:
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
import
Text.Shakespeare.Text
import
qualified
Data.Text.Lazy.IO
as
TLIO
import
Data.Text
(
Text
)
import
Control.Monad
(
forM_
)
data
Item
=
Item
{
itemName
::
Text
,
itemQty
::
Int
}
items
::
[
Item
]
items
=
[
Item
"apples"
5
,
Item
"bananas"
10
]
main
::
IO
()
main
=
forM_
items
$
\
item
->
TLIO
.
putStrLn
[
lt
|
You
have
#
{
show
$
itemQty
item
}
#
{
itemName
item
}
.|
]
Some quick points about this simple example:
General Recommendations
Here are some general hints from the Yesod community on how to get the most out of Shakespeare:
-
For actual sites, use external files. For libraries, it’s OK to use quasiquoters, assuming they aren’t too long.
-
Patrick Brisbin has put together an immensely helpful Vim code highlighter.
-
You should almost always start Hamlet tags on their own line instead of embedding start/end tags after an existing tag. The only exception to this is the occasional
<i>
or<b>
tag inside a large block of text.
Get Developing Web Apps with Haskell and Yesod, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.