Happstack supports a number of third party templating and HTML libraries. It is easy to add support for additional libraries, if your favorite does not already have support.
Each templating system has it's own set of advantages and drawbacks.
The BlazeHtml library provides combinators for generating HTML 4 and HTML 5 in Haskell.
pros:
cons:
Html
monad is not a real monad, nor is it a monad transformer. This eliminates some advantage usage possibilities.HSP allows you to embed literal XML syntax inside your Haskell code. A pre-processor rewrites the literal XML into normal haskell function calls, and then the code is compiled.
pros:
cons:
It is trivial to use BlazeHtml with Happstack. Essentially you just use toResponse
to convert a blaze Html
value into a Response
. For more detailed information on using BlazeHtml, see the BlazeHtml website. The following example should get you started:
> {-# LANGUAGE OverloadedStrings #-} > module Main where > > import Happstack.Server > import Text.Blaze ((!)) > import qualified Text.Blaze.Html4.Strict as H > import qualified Text.Blaze.Html4.Strict.Attributes as A > > appTemplate :: String -> [H.Html] -> H.Html -> H.Html > appTemplate title headers body = > H.html $ do > H.head $ do > H.title (H.toHtml title) > H.meta ! A.httpEquiv "Content-Type" ! A.content "text/html;charset=utf-8" > sequence_ headers > H.body $ do > body > > helloBlaze :: ServerPart Response > helloBlaze = > ok $ toResponse $ > appTemplate "Hello, Blaze!" > [H.meta ! A.name "keywords" ! A.content "happstack, blaze, html"] > (H.p "hello, blaze!") > > main :: IO () > main = simpleHTTP nullConf $ helloBlaze
[Source code for the app is here.]
Now if we visit http://localhost:8000/, we will get an html page which says:
hello, blaze!
This example is pretty simple, but there are a few things to note:
appTemplate
function is purely BlazeHtml code and is in no way Happstack specific.appTemplate
is purely a stylistic choice.To enable HSX support, you must install the happstack-hsp package.
HSX is an XML-based templating system that allows you to embed XML in your Haskell source files. If you have ever had to use PHP, you may want to run screaming from this idea. However, the HSX solution is far saner than the PHP solution, so you may want to give it a chance.
The first thing we will see is a funny OPTIONS_GHC
pragma at the top of our file:
> {-# LANGUAGE FlexibleContexts, OverlappingInstances #-} > {-# OPTIONS_GHC -F -pgmF trhsx #-} > module Main where >
HSX works by running the code through an external pre-processor named trhsx
. This pragma at the top is how we tell GHC that this file needs to be run through the trhsx
pre-processor in order to work. So, that options line looks a bit like line noise. You can try to remember it like this:
-F
says we want to filter the source code (or maybe transForm the source code)-pgmF
specifies the program we want to do the transformationtrhsx
is short for transform using hsxNext we have some imports:
> import Control.Applicative ((<$>)) > import Control.Monad.Identity (Identity(runIdentity)) > import Data.String (IsString(fromString)) > import Data.Text (Text) > import qualified HSX.XMLGenerator as HSX > import Happstack.Server.HSP.HTML > import Happstack.Server (Request(rqMethod), ServerPartT, askRq, nullConf, simpleHTTP) > import HSP.Identity () -- instance (XMLGen Identity) >
Now we can define a function which generates an HTML page:
> hello :: ServerPartT IO XML > hello = unXMLGenT > <html> > <head> > <title>Hello, HSP!</title> > </head> > <body> > <h1>Hello HSP!</h1> > <p>We can insert Haskell expression such as this: <% sum [1 .. (10 :: Int)] %></p> > <p>We can use the ServerPartT monad too. Your request method was: <% getMethod %></p> > <hr/> > <p>We don't have to escape & or >. Isn't that nice?</p> > <p>If we want <% "<" %> then we have to do something funny.</p> > <p>But we don't have to worry about escaping <% "<p>a string like this</p>" %></p> > <p>We can also nest <% <span>like <% "this." %> </span> %></p> > </body> > </html> > where > getMethod :: XMLGenT (ServerPartT IO) String > getMethod = show . rqMethod <$> askRq >
> main :: IO () > main = simpleHTTP nullConf $ hello >
The first thing we notice is that syntax looks pretty much like normal HTML syntax. There are a few key differences though:
The syntax:
allows us to embed a Haskell expression inside of literal XML.
As shown in this line:
> <p>We can also nest <% <span>like <% "this." %> </span> %></p>
we can freely nest Haskell and XML expressions.
In order to use HSX it is very useful to understand what is actually going on behind the magic. If we have the line:
> foo :: XMLGenT (ServerPartT IO) XML > foo = <span class="bar">foo</span>
and we run trhsx
, it gets turned into a line like this:
> foo :: XMLGenT (ServerPartT IO) XML > foo = genElement (Nothing, "span") [ asAttr ("class" := "bar") ] [asChild ("foo")] >
We see that the XML syntax has simply been translated into normal haskell function calls.
HSX
types and classesThere are a few types and classes that you will need to be familiar with.
XMLGenT
typeThe first type is the XMLGenT
monad transformer:
> newtype XMLGenT m a = XMLGenT (m a)
> -- | un-lift. > unXMLGenT :: XMLGenT m a -> m a > unXMLGenT (XMLGenT ma) = ma
This seemingly useless type exists solely to make the type-checker happy. Without it we would need an instance like:
> instance (EmbedAsChild (IdentityT m) a, Functor m, Monad m, m ~ n) => > EmbedAsChild (IdentityT m) (n a) where > asChild = ...
Unfortunately, because (n a)
is so vague, that results in overlapping instances that cannot be resolved without IncohorentInstances
. And, in my experience, enabling IncohorentInstances
is never the right solution.
So, when generating XML you will generally need to apply unXMLGenT
to the result to remove the XMLGenT
wrapper as we did in the hello
function. Anyone who can figure out to do away with the XMLGenT
class will be my personal hero.
XMLGen
classNext we have the XMLGen
class:
> class Monad m => XMLGen m where > type XML m > data Child m > data Attribute m > genElement :: Name > -> [XMLGenT m [Attribute m]] > -> [XMLGenT m [Child m]] > -> XMLGenT m (XML m) > genEElement :: Name > -> [XMLGenT m [Attribute m]] > -> XMLGenT m (XML m) > genEElement n ats = genElement n ats [] > xmlToChild :: XML m -> Child m > pcdataToChild :: String -> Child m
Most of these functions and types are used internally and not used directly by the developer.
You will notice that we have a type-class instead of just simple functions and types. One feature of HSX is that it is not tied to any particular XML representation. Instead, the XML representation is based on the monad we are currently inside. For example, inside of a javascript monad, we might generate javascript code that renders the XML, inside of another monad, we might generate the Node
type used by the heist
template library. We will see some examples of this in a later section.
The data
and type
declarations appearing inside the class declaration are allowed because of the TypeFamilies
extension. For a detailed coverage of type families see this wiki entry.
XML m
type synonymThe XMLGen
type-class defines an associated type synonym XML m
:
> type XML m
XML m
is a synonym for whatever the xml type is for the monad m
. We can write an XML fragment that is parameterized over an arbitrary monad and xml type like this:
> bar :: (XMLGenerator m) => XMLGenT m (HSX.XML m) > bar = <span>bar</span> >
Note that we had this qualified import:
> import qualified HSX.XMLGenerator as HSX
That is because we need to differentiate the XML
associated type synonym from the plain-old XML
data type that is declared elsewhere. Having two types with the same name is a bit silly, but that is the way it is for now.
EmbedAsChild
classThe EmbedAsChild
is used to turn a value into a list of children of an element:
> type GenChildList m = XMLGenT m [Child m] > > -- | Embed values as child nodes of an XML element. The parent type will be clear > -- from the context so it is not mentioned. > class XMLGen m => EmbedAsChild m c where > asChild :: c -> GenChildList m
There are generally many instances of EmbedAsChild
allowing you to embed String
, Text
, Int
, and other values. You might find it useful to create additional instances for types in your program. We will some some examples later in this tutorial.
To use the EmbedAsChild
class we us the <% %>
syntax shown earlier. For example, when we write:
> a :: (XMLGenerator m) => GenChildList m > a = <% 'a' %> >
It gets turned into:
> a :: (XMLGenerator m) => GenChildList m > a = (asChild ('a')) >
EmbedAsAttr
classThe EmbedAsAttr
class is similar to the EmbedAsChild
class. It is used to turn arbitrary values into element attributes.
> type GenAttributeList m = XMLGenT m [Attribute m] > > -- | Similarly embed values as attributes of an XML element. > class XMLGen m => EmbedAsAttr m a where > asAttr :: a -> GenAttributeList m
If we have some attributes like this:
> foo = <span class="foo" size=(80 :: Int) bogus=False>foo</span>
It will get translated to:
> foo > = (genElement (Nothing, "span") > [asAttr ("class" := "foo"), asAttr ("size" := (80 :: Int)), > asAttr ("bogus" := False)] > [asChild ("foo")])
which might be rendered as:
XMLGenerator
classYou may have noticed that some of the examples had a class constraint (XMLGenerator m)
:
> bar :: (XMLGenerator m) => XMLGenT m (HSX.XML m) > bar = <span>bar</span> >
XMLGenerator
is just a class alias. It is defined as such:
> class ( XMLGen m > , SetAttr m (HSX.XML m) > , AppendChild m (HSX.XML m) > , EmbedAsChild m (HSX.XML m) > , EmbedAsChild m [HSX.XML m] > , EmbedAsChild m String > , EmbedAsChild m Char > , EmbedAsAttr m (Attr String String) > , EmbedAsAttr m (Attr String Int) > , EmbedAsAttr m (Attr String Bool) > ) => XMLGenerator m
It contains a list of common instances that all xml generation monads are expected to provide. It just saves you from having to list all thoses instances by hand when you use them.
First we have a simple function to render the pages and print them to stdout:
> printXML :: Identity XML -> IO () > printXML = putStrLn . renderAsHTML . runIdentity
do
syntaxIt is possible to use hsx markup inside a do
-block. If you are using an older version of hsx, you just need to be aware of one little catch. In this example:
> doBlock :: (XMLGenerator m) => XMLGenT m (HSX.XML m) > doBlock = > do <div> > <p>A child element</p> > </div>
Notice that we indent the closing </div> tag. That indentation rule is consistent with the specification for how do-notation works. It is intend for the same reason that if .. then .. else ..
blocks have to be idented in a special way inside do
-blocks.
In newer versions of HSX, this restriction has been lifted.
defaultTemplate
There is a bit of boiler plate that appears in ever html document such as the <html>, <head>, <title>, and <body> tags. The defaultTemplate
function provides a minimal skeleton template with those tags:
> defaultTemplate :: ( XMLGenerator m > , EmbedAsChild m body > , EmbedAsChild m headers > ) => > String -- string to put in <title> > -> headers -- additional elements to put in <head> > -> body -- elements to put in <body> > -> m (HSX.XML m)
defaultTemplate
requires that we pass in headers
and a body
. But what if we don't have any headers that we want to add?
Most XMLGenerator
monads provide an EmbedAsChild m ()
instance, such as this one:
> instance EmbedAsChild Identity () where > asChild () = return []
So, we can just pass in ()
like so:
> empty :: IO () > empty = printXML $ defaultTemplate "empty" () ()
Which will render as such:
<html ><head ><title >empty</title ></head ><body ></body ></html >
Sometimes we want to create a number of child elements without knowing what their parent element will be. We can do that using the:
syntax. For example, here we return two paragraphs:
> twoParagraphs :: (XMLGenerator m) => XMLGenT m [HSX.Child m] > twoParagraphs = > <%> > <p>Paragraph one</p> > <p>Paragraph two</p> > </%>
We can embed those in parent element like this:
> twoParagraphsWithParent :: (XMLGenerator m) => XMLGenT m (HSX.XML m) > twoParagraphsWithParent = > <div> > <% twoParagraphs %> > </div>
if .. then .. else ..
Using an if .. then .. else ..
is straight-foward. But what happens
when you don't really want an else
case? This is another place we
can use ()
:
> ifThen :: Bool -> IO () > ifThen bool = > printXML $ defaultTemplate "ifThen" () $ > <div> > <% if bool > then <% > <p>Showing this thing.</p> > %> > else <% () %> > %> > </div> >
Normally attributes are added to an element using the normal html attribute syntax. HSX, has a special extension where the last attribute can be a Haskell expression which returns a list of attributes to add to the element. For example:
> attrList :: IO () > attrList = > printXML $ defaultTemplate "attrList" () $ > <div id="somediv" ["class" := "classy", "title" := "untitled"] > > </div> >
The type of the elements of the list can be anything with an EmbedAsAttr m a
instance. In this case we create a list of Attr
values:
> data Attr n a = n := a
We can use this feature to conditionally add attributes using a simple if .. then .. else ..
statment:
> optAttrList :: Bool -> IO () > optAttrList bool = > printXML $ defaultTemplate "attrList" () $ > <div id="somediv" (if bool > then ["class" := "classy", "title" := "untitled"] > else []) > > </div> >
[Source code for the app is here.]
One drawback to HSX is that it can result in some pretty ugly (and sometimes very long) error messages. Fortunately, the errors are almost always the same type of thing, so after a little experience it is easy to see what is going wrong. Here are some tips if you run into errors:
As we saw, trhsx transforms the literal XML into normal Haskell code. Unfortunately, the error positions reported by GHC reflect where the error occurred in the transformed code, not the original input. HSX tries to help GHC by inserting LINE pragmas. While that helps to a degree, it still leaves a fair bit of fuzz.
The trick is to look towards the bottom of the error message where it will usually show you the expression that contained the error. For example, if we have:
> typeError :: XMLGenT (ServerPartT IO) XML > typeError = <foo><% 1 + 'a' %></foo>
We will get an error like:
TemplatesHSP.markdown.lhs:459:59: No instance for (Num Char) arising from a use of `+' Possible fix: add an instance declaration for (Num Char) In the first argument of `asChild', namely `(1 + 'a')' In the first argument of `asChild', namely `((asChild (1 + 'a')))' In the expression: asChild ((asChild (1 + 'a')))
The last line says:
In the expression: asChild ((asChild (1 + 'a')))
which is, indeed, where the type error is.
A bug report about the line number issue has been filed, and there are ideas on how to fix it. You can read more here.
Another common error is that of overlapping instances. For example, if we wrote the following:
> overlapping = <p>overlapping</p>
We would get an error like:
TemplatesHSP.markdown.lhs:495:36: Overlapping instances for EmbedAsChild m0 [Char] arising from a use of `asChild' Matching instances: instance [overlap ok] HSX.XMLGen m => EmbedAsChild m String -- Defined in `HSX.XMLGenerator' instance EmbedAsChild Identity String -- Defined in `HSP.Identity' instance Monad m => EmbedAsChild (ServerPartT m) String -- Defined in `HSP.ServerPartT' (The choice depends on the instantiation of `m0' To pick the first instance above, use -XIncoherentInstances when compiling the other instance declarations) In the expression: asChild ("overlapping") In the third argument of `genElement', namely `[asChild ("overlapping")]' In the expression: (genElement (Nothing, "p") [] [asChild ("overlapping")])
I have never enabled IncoherentInstances
and actually had it do what
I wanted. In this case, the solution is to add an explicit type
signature that mentions the missing constraint:
> overlapping :: (EmbedAsChild m String) => XMLGenT m (HSX.XML m) > overlapping = <p>overlapping</p>
In general, there can be a lot of required EmbedAsChild
and
EmbedAsAttr
instances. So, often times you can save a lot of typing
by using the XMLGenerator
class alias:
> overlapping' :: (XMLGenerator m) => XMLGenT m (HSX.XML m) > overlapping' = <p>overlapping</p>
Sometimes a type signature for the parent function is not enough. For example, let's say we have:
> ambiguous :: (EmbedAsChild m String) => XMLGenT m (HSX.XML m) > ambiguous = <p><% fromString "ambiguous" %></p>
That will generate an error like this one:
TemplatesHSP.markdown.lhs:557:28: Ambiguous type variable `c0' in the constraints: (IsString c0) arising from a use of `fromString' at TemplatesHSP.markdown.lhs:557:28-37 (EmbedAsChild m c0) arising from a use of `asChild' at TemplatesHSP.markdown.lhs:557:19-25 Probable fix: add a type signature that fixes these type variable(s) In the first argument of `asChild', namely `(fromString "ambiguous")' In the first argument of `asChild', namely `((asChild (fromString "ambiguous")))' In the expression: asChild ((asChild (fromString "ambiguous"))) Failed, modules loaded: none.
Here we are trying to use fromString
to convert "ambiguous"
into some type, and then we embed that type using asChild
. But there is not enough information to figure out what the intermediate type should be. It is the same problem we have if we try to write:
> \str -> show (read str)
The solution here is to add an explicit type signature to the result of fromString
:
> ambiguous :: (EmbedAsChild m Text) => XMLGenT m (HSX.XML m) > ambiguous = <p><% (fromString "ambiguous") :: Text %></p>
OverloadedStrings
Unfortunately, HSX and the OverloadedStrings
extension do not play together nicely. If we were to write:
> overloaded :: XMLGenT Identity XML > overloaded = <p>Hello</p>
That would be transformed into:
> overloaded = (genElement (Nothing, "p") [] [asChild ("Hello")])
However, with the OverloadStrings
extension enabled, the compiler will automatically insert fromString
in front of every string literal like this:
> overloaded = (genElement (Nothing, fromString "p") [] [asChild (fromString "Hello")])
Which results in the ambiguous error as we saw in the last section.
The best workaround for this at the moment is to not use OverloadedStrings
and HSX
in the same module. If you really want OverloadedStrings
in some of your code, then you might want to put all your HSX templates in a separate module that does not use the extension. Aside from working around the issue, it can also help to improve your code structure. Many people frown on mixing the presentation and business logic layers. Putting your templates in separate modules can help keep the presentation and business layer separate.
Finding a better solution is still an open problem which has not been examined yet. It would be great if HSX played nicely with OverloadedStrings
. It would also be nice to create an XML type for use with HSX that was based around Text
. These two things are a bit intertwined since you generally want to use OverloadedStrings
when using Text
.
You will need to install happstack-hsp and shakespeare-i18n for this section.
Internationalization (abbreviated to the numeronym i18n) and localization (L10n) generally refer to the processing of making an application usuable by people that speak different languages, use different alphabets and keyboards, and have different conventions for things like formatting times and dates, currency, etc.
Proper handling of these issues can run deep into your code. For example, English speakers often think of people as having a first name and a last name -- but when you look at how people's names are used around the world, you realize these familiar terms are not universally applicable. So, a type like:
> data Name = Name { firstName :: Text, lastNime :: Text } >
may not be sufficient.
The haskell wiki lists a bunch of methods for translating strings into multiple languages.
In this example, we show how we can use native haskell types datas, a
translator friendly file format, and HSP to do some simple
internationalization. We will build on top of the shakespeare-i18n
library.
As usual, we start off with a bunch of imports and pragmas:
> {-# LANGUAGE FlexibleContexts, FlexibleInstances, TemplateHaskell, > MultiParamTypeClasses, OverloadedStrings #-} > {-# OPTIONS_GHC -F -pgmFtrhsx #-} > module Main where > > import Control.Applicative ((<$>)) > import Control.Monad (msum) > import Control.Monad.Reader (ReaderT, ask, runReaderT) > import Control.Monad.Trans (MonadIO(liftIO)) > import Data.Map (Map, fromList) > import qualified Data.Map as Map > import Data.Text (Text) > import qualified Data.Text as Text > import Happstack.Server ( ServerPart, ServerPartT, dir, lookTexts', mapServerPartT > , nullConf, nullDir, queryString, simpleHTTP > , acceptLanguage, bestLanguage > ) > import Happstack.Server.HSP.HTML > import qualified HSX.XMLGenerator as HSX > import Text.Shakespeare.I18N ( RenderMessage(..), Lang, mkMessage, mkMessageFor > , mkMessageVariant) > import System.Random (randomRIO) >
Instead of using strings directly in our templates we could create a data type where each constructor represents a phrase, sentence, or paragraph that we want to put on the page. For example, we could define the type:
> data Message = Hello | Goodbye
Then we could provide a translation function for each language we support:
> translation_en :: Message -> Text > translation_en Hello = Text.pack "hello" > translation_en Goodbye = "goodbye" > > translation_lojban :: Message -> Text > translation_lojban Hello = "coi" > translation_lojban Goodbye = "co'o" > > translations :: Map Text (Message -> Text) > translations = > fromList [ ("en" , translation_en) > , ("lojban", translation_lojban) > ] > > translate :: Text -> Message -> Text > translate lang msg = > case Map.lookup lang translations of > Nothing -> "missing translation" > (Just translator) -> > translator msg >
and then in our templates we can write:
> helloPage :: (XMLGenerator m, EmbedAsChild m Text) => Text -> XMLGenT m (HSX.XML m) > helloPage lang = > <html> > <head> > <title><% translate lang Hello %></title> > </head> > <body> > <p><% translate lang Hello %></p> > </body> > </html> >
The principle behind this approach is nice, but in practice, it has a few problems:
having to write the translation functions in the Haskell source is not a very friendly format for the people who will be doing the translations.
having to call 'translate' explicitly is boring, tedious, and error prone
having to pass around the desired 'lang' manually is also boring, tedious, and error prone
Fortunately, we can work around all these issues quite simply.
RenderMessage
classshakespeare-i18n
provides a simple class for providing translations:
> type Lang = Text > > class RenderMessage master message where > renderMessage :: master -- ^ translation variant > -> [Lang] -- ^ desired languages in descending order of preference > -> message -- ^ message we want translated > -> Text -- ^ best matching translation
renderMessage
is pretty straight-forward. It takes a list of preferred languages and a message datatype (such as Message
type we defined above) and returns the best matching translation. The only mysterious part is the master
argument. (Personally, I think variant
might be a better name for the argument). The argument exists so that you can provide more than one set of translations for the same message type.
For example, let's say that we had defined the Message
type in a library. Being the nice people we are, we also provide a set of translations for the Message
type. However, someone using our library may want to provide a completely different set of translations that are more appropriate to their application. For example, in the library we might have:
> data LibraryI18N = LibraryI18N > > instance RenderMessage LibraryI18N Message where > renderMessage = ...
But the user could provide their own translations for Message
via:
> data AppI18N = AppI18N > > instance RenderMessage AppI18N Message where > renderMessage = ...
Writing the translations in your Haskell source can be pretty inconvenient. Especially if you are working with a team of outsourced translators. Fortunately, shakespeare-i18n
has support for external translation files.
To keep things simple:
.msg
where lang
is a language code such as en
, en-GB
, fr
, etc.msg
files must be UTF-8 encodedSo for this example we will have three files:
messages/standard/en.msg messages/standard/en-GB.msg messages/standard/jbo.msg
en.msg
is a set of generic English translations.en-GB.msg
is a set of English translations using spellings and idioms common to Great Britainjbo.msg
is a set of Lojban translationsThe contents of the files are:
messages/standard/en.msg
Hello: greetings Goodbye: seeya Problems n@Int thing@Thing: Got #{show n} #{plural_en n "problem" "problems" } but a #{thing_tr "en" thing} ain't #{plural_en n "it" "one"}.
messages/standard/en-GB.msg
Hello: all right? Goodbye: cheerio Problems n thing: Got #{show n} #{plural_en n "problem" "problems" } but a #{thing_tr "en-gb" thing} ain't one.
messages/standard/jbo.msg
Hello: coi Goodbye: co'o
The format is very simple. Each line looks like:
Constructor arg0 arg1 .. argn: translation text
Constructor
is a valid Haskell constructor name that we will use to reference this translation:
You may also notice that in en.msg
the arguments contain types like n@Int
. And some of translations contain markup like #{show n}
. You can probably guess what those things mean -- we will come back to them shortly.
You may also notice that the Lojban translation is missing the Problems
constructor. Since there is no translation provided, renderMessage
will use the default translation (which, in this case will come from en.msg
).
To load the message files we first need to define our master
type:
> data DemoApp = DemoApp >
Then we just call mkMessage
:
> mkMessage "DemoApp" "messages/standard" ("en") >
mkMessage
is a Template Haskell function which:
.msg
filesRenderMessage
instancemkMessage
has the following type:
> mkMessage :: String -- ^ name of master translation type > -> FilePath -- ^ path to folder which contains the `.msg` files > -> Lang -- ^ default language > -> Q [Dec]
If we use -ddump-splices
we see that the mkMessages
call above generated the following for us:
> data DemoAppMessage > = MsgHello > | MsgGoodbye > | MsgProblems { translationsMessageN :: Int > , translationsMessageThing :: Thing > } > > > instance RenderMessage DemoApp DemoAppMessage where > renderMessage = ...
It has created a new type for us DemoAppMessage
where each constructor is derived from the constructors found in the en.msg
file. The constructor names all have the prefix Msg
. That is just to avoid name collisions with the other constructors in your application.
It has also created a RenderMessage
instance with all the translations (not shown for the sake of readability).
Now we can do:
*Main> renderMessage DemoApp ["en"] MsgHello "greetings"
Note that because the message files are read in using Template Haskell at compile time, we do not need to install them on the live server. Also, if you change the .msg
files, you will not see the changes until you recompile.
#{ }
, and pluralsThe Problems
constructor in the en.msg
file appears considerably more complicate than the Hello
and Goodbye
cases:
Problems n@Int thing@Thing: Got #{show n} #{plural_en n "problem" "problems" } but a #{thing_tr "en" thing} ain't #{plural_en n "it" "one"}.
There are a few things going on here.
The Problems
constructor takes two arguments: n
and thing
. In order to create the MsgProblems
constructor, mkMessage
needs to know the types of those arguments. So, we add the type annotations using the @
syntax. We only need the type annotations in the default translation file. The default translation file is specified as the third argument to mkMessage
-- which in this example is "en"
.
The types of the arguments can be any valid Haskell type. In this case 'Int' and 'Thing'. 'Thing' is just a normal Haskell datatype which we will define right now as:
> data Thing = TypeError | SegFault deriving (Enum, Bounded, Show)
The #{ }
syntax allows you to call a Haskell function and splice the result into the message. For example:
> #{show n}
will convert n
to a String
and splice the String
into the message. The expression inside the #{ }
must be a pure expression and it must have a type that is an instance of the ToMessage
class:
> class ToMessage a where > toMessage :: a -> Text
By default, only String
and Text
have ToMessage
instances.
Remember that mkMessage
generates code which gets spliced into the current module. That means the code inside #{ }
has access to any functions and types which are available in the module that calls mkMessage
.
In English, we say:
In our translations, we don't want to say I have 1 problem(s). We can handle this pluralization issue by creating a simple helper function such as this one:
> plural_en :: (Integral i) => i -> String -> String -> String > plural_en 1 x _ = x > plural_en _ _ y = y
Looking at en.msg
you notice that we need to use plural_en
twice to make the grammar sound natural. When creating messages is good to use whole phrases and sentences because changes in one part of a sentence can affect other parts of the sentence. Rules about plurals, word order, gender agreement, etc, vary widely from one language to the next. So it is best to assume as little as possible and give the translators as much flexibility as possible.
mkMessage
creates a new type from the constructors it finds in the .msg
files. But sometimes we want to create a translation for an existing type. For example, we need to translate the Thing
type. We can do that by creating a function like:
> thing_tr :: Lang -> Thing -> Text
Which we can call in the translation file like:
> #{thing_tr "en" thing}
But, how do we implement thing_tr
? One option is to simply write a function like:
> thing_tr :: Lang -> Thing -> Text > thing_tr lang TypeError | lang == "en" = "type error" > thing_tr lang SegFault | lang == "en" = "segmentation fault" > thing_tr _ thing = thing_tr "en" thing
But, now someone has to update the Haskell code to add new translations. It would be nice if all the translations came from .msg
files.
The mkMessageFor
function allows us to create translations for an existing type:
> mkMessageFor :: > String -- ^ master type > -> String -- ^ data to translate > -> FilePath -- ^ path to `.msg` files > -> Lang -- ^ default language > -> Q [Dec]
We can create a set of .msg
files for the Thing
type like this (note the file path):
messages/thing/en.msg
TypeError: type error SegFault: seg fault
And then use mkMessageFor
to create a RenderMessage
instance:
> mkMessageFor "DemoApp" "Thing" "messages/thing" "en"
That will create this instance for us:
> -- autogenerated by `mkMessageFor` > instance RenderMessage DemoApp Thing where > renderMessage = ...
Because mkMessageFor
is creating a RenderMessage
for an existing type, it does not need to append Message
to the type name or prefix the constructors with Msg
. Now we can define our thing_tr
function like this:
> thing_tr :: Lang -> Thing -> Text > thing_tr lang thing = renderMessage DemoApp [lang] thing
This is definitely a bit roundabout, but it is the best solution I can see using the existing shakespeare-i18n
implementation.
We can use mkMessageVariant
to create an alternative set of
translations for a type that was created by mkMessage
. For example:
> data DemoAppAlt = DemoAppAlt > > mkMessageVariant "DemoAppAlt" "DemoApp" "messages/alt" "en" >
HSX
templatesTo use the DemoAppMessage
type in an HSX
template, all we need is an EmbedAsChild
instance.
The instance will need to know what the client's preferred languages
are. We can provide that by putting the users language preferences in
a ReaderT
monad:
> type I18N = ServerPartT (ReaderT [Lang] IO) >
Next we create the EmbedAsChild
instance:
> instance EmbedAsChild I18N DemoAppMessage where > asChild msg = > do lang <- ask > asChild $ renderMessage DemoApp lang msg >
Now we can use the message constructors inside our templates:
> pageTemplate :: (EmbedAsChild I18N body) => String -> body -> I18N XML > pageTemplate title body = > defaultTemplate title () > <div> > <% body %> > <ul> > <% mapM (\lang -> > <li> > <a [ ("href" :: String) := ("?_LANG="++ lang)]><% lang %></a> > </li>) > (["en", "en-GB", "jbo"] :: [String]) %> > </ul> > </div> > > homePage :: I18N XML > homePage = > pageTemplate "home" > <p><% MsgHello %></p> > > goodbyePage :: I18N XML > goodbyePage = > pageTemplate "goodbye" > <p><% MsgGoodbye %></p> > > problemsPage :: Int -> Thing -> I18N XML > problemsPage n thing = > pageTemplate "problems" > <p><% MsgProblems n thing %></p> >
Instead of putting text in the <p> </p>
tags we just use our message constructors.
Getting the language preferences from ReaderT [Lang]
is just one possibility. Your application may already have a place to store session data that you can get the preferences from, or you might just stick the preferences in a cookie.
The Accept-Language
header is sent by the client and, in theory, specifies what languages the client prefers, and how much they prefer each one. So, in the absence of any additional information, the Accept-Language
header is a good starting place. You can retrieve and parse the Accept-Language
header using the acceptLanguage
function and then sort the preferences in descending order using bestLanguage
:
> acceptLanguage :: (Happstack m) => m [(Text, Maybe Double)] > bestLanguage :: [(Text, Maybe Double)] -> [Text]
You should not assume that the Accept-Language
header is always correct. It is best to allow the user a way to override the Accept-Language
header. That override could be stored in their user account, session data, a cookie, etc. In this example we will just use a QUERY_STRING
parameter _LANG
to override the Accept-Language
header.
We can wrap this all up in a little function that converts our I18N
part into a normal ServerPart
:
> withI18N :: I18N a -> ServerPart a > withI18N part = > do langsOverride <- queryString $ lookTexts' "_LANG" > langs <- bestLanguage <$> acceptLanguage > mapServerPartT (flip runReaderT (langsOverride ++ langs)) part >
And finally, we just have our route
table and main
function:
> routes :: I18N XML > routes = > msum [ do nullDir > homePage > , dir "goodbye" $ goodbyePage > , dir "problems" $ > do n <- liftIO $ randomRIO (1, 99) > let things = [TypeError .. SegFault] > index <- liftIO $ randomRIO (0, length things - 1) > let thing = things !! index > problemsPage n thing > ] > > > main :: IO () > main = simpleHTTP nullConf $ withI18N routes
[Source code for the app is here.]
[You will also need to download and unzip the message files here.]
In this section we showed how to use HSX
and Happstack.Server.I18N
, and shakespeare-i18n
together to provide an i18n solution. However, there are no dependencies between those libraries and modules. So, you can use other solutions to provide translations for HSX
, or you can use shakespeare-i18n
with other template systems.
One thing that would make shakespeare-i18n
better is a utility to help keep the .msg
files up-to-date. I have describe my ideas for a tool here. We just need a volunteer to implement it.
Heist is an XML templating engine. The static HTML portions of your web pages reside in XML files which can be edited and reloaded with out having to recompile your server. The dynamic portions are generated in Haskell and spliced into the templates.
To enable Heist support, you must install the happstack-heist package. It is not installed by default.
The following template is almost an XHTML document, except that it contains the special tag <fact>6</fact>:
<html> <head> <title>Factorial Page</title> </head> <body> <h1>Factorial Page</h1> <p>The factorial of 6 is <fact>6</fact></p> </body> </html>
The <fact/> tag is an application specific tag which performs a factorial and splices in the result.
The following example shows how to initialize the Heist template system and how to create your own custom tags.
First a bunch of boring imports:
> module Main where
> import Control.Monad (msum) > import Control.Monad.Trans (MonadIO) > import qualified Data.Text as T > import Happstack.Server (dir, nullConf, nullDir, simpleHTTP, seeOther, toResponse) > import Happstack.Server.Heist (templateServe, templateReloader) > import Text.Templating.Heist (HeistT, Template, HeistState > , bindSplice, defaultHeistState, getParamNode) > import Text.Templating.Heist.TemplateDirectory (newTemplateDirectory') > import qualified Text.XmlHtml as X
Next we have the factorial splice:
> factSplice :: (Monad m) => HeistT m Template > factSplice = do > input <- getParamNode > let text = T.unpack $ X.nodeText input > n = read text :: Int > return [X.TextNode $ T.pack $ show $ product [1..n]] >
The splice runs in the HeistT
monad transformer. The getParamNode
function:
> getParamNode :: (Monad m) => HeistT m Node
returns the XHTML node that triggered this splice function to be called. In this case it would be, <fact>6</fact>
We then use textContent
:
> X.textContent :: Node -> ByteString
to extract the string "6" from the node, which we convert to an Int.
Finally, we calculate the factorial, and convert the result back into the XML that we want to splice into the template.
The mapping from tag names to template functions is stored in the HeistState m
. New tags can be added by using bindSplice
:
> bindSplice :: Monad m => > ByteString -- ^ name to use for splice tag > -> HeistT m Template -- ^ template function to handle the splice > -> HeistState m -- ^ template state to update > -> HeistState m
So here we bind <fact/> to factSplice
> templateState :: (MonadIO m) => > FilePath -- ^ path to template directory > -> HeistState m > templateState templateDir = bindSplice (T.pack "fact") factSplice defaultHeistState >
In our main function, we must first initialize the Heist template system by using newTemplateDirectory'
:
> newTemplateDirectory' :: (MonadIO m, MonadIO n) => > FilePath -- ^ path to template directory on disk > -> HeistState m -- ^ the template state > -> n (TemplateDirectory m) -- ^ a handle to the template directory
In this example, we would put the templates in same directory the app is running from:
> main :: IO () > main = do > let templateDir = "." > td <- newTemplateDirectory' templateDir (templateState templateDir)
to serve templates we simply use the templateServe
function:
> templateServe :: (ServerMonad m, MonadPlus m, MonadIO m) => > TemplateDirectory m -- ^ the handle returned by newTemplateDirectory' > -> m Response
templateServe
will look at the path in the URL, add .tpl to the end, and try to find a matching template file on disk to return.
Because the templates are loaded into memory, updating the files on disk will not have any immediate effect. You can use templateReloader
to force the templates to be reloaded:
> templateReloader :: (MonadIO m, MonadIO n) => > TemplateDirectory m -- ^ handle returned by newTemplateDirectory' > -> n Response
so putting those together we get our handlers:
> simpleHTTP nullConf $ msum > [ templateServe td > , dir "reload" $ nullDir >> templateReloader td > , nullDir >> seeOther "/factorial" (toResponse ()) > ]
[Source code for the app is here. You will also need to download the source for factorial.tpl and save it in the same directory as TemplateHeist.hs.]
If you point your browser at http://localhost:8000/factorial you should see the factorial page. You can point your browser at http://localhost:8000/reload to reload the template.
Heist offers a variety of other features not shown here. We have only covered the Happstack integration aspects. For more general information on Heist look at the Official Heist Template Tutorial and the Heist Haddock Documenation.
To use JMacro with happstack and hsx, you should install the hsx-jmacro and happstack-jmacro packages. You will also need to be sure that your version of happstack-hsp is >= 6.1.0.
JMacro is a library that makes it easy to include javascript in your templates.
The syntax used by JMacro is almost identical to JavaScript. So, you do not have to learn some special DSL to use it. In fact, JMacro can work with most JavaScript you find in the wild. Using JMacro has a number of advantages over just using plain-old javascript.
syntax checking ensures that your JavaScript is syntactically valid at compile time. That eliminates many common JavaScript errors and reduces development time.
hygienic names and scoping automatically and transparently ensure that blocks of JavaScript code do not accidentally create variables and functions with conflicting names.
Antiquotation, marshalling, and shared scope make it easy to splice Haskell values into the JavaScript code. It also makes it easy to programmatically generate JavaScript code.
The hsx-jmacro and happstack-jmacro libraries makes it easy to use JMacro with Happstack and HSP.
The following examples demonstrate the basics of JMacro and how it interfaces with HSX and Happstack. The examples are intended to demonstrate what is possible with JMacro. The examples are not intended to demonstrate good javascript practices. For example, many developers frown on the use of the onclick
attribute in html, or having <script>
tags in the <body>
.
The JMacro library does not require any external pre-processors. Instead it uses the magic of QuasiQuotation.
QuasiQuotes
can be enabled via the LANGUAGE
extension:
> {-# LANGUAGE CPP, FlexibleInstances, GeneralizedNewtypeDeriving, > TypeSynonymInstances, QuasiQuotes #-}
In this example we are also using HSX, which does require a pre-processor. (A crash course section on HSX itself will be coming soon). The following line will automatically run the pre-processor for us (and also suppress warnings about orphan instances):
> {-# OPTIONS_GHC -F -pgmFtrhsx -fno-warn-orphans #-}
Next we have a boatload of imports. Not all of these are required to use JMacro. Many are just used for the demos.
There is one really import thing to note though. If you look at the import for Language.Javascript.JMacro
, you will find that there are a bunch of things imported like jsVarTy
which we never call explicitly in this demo. The calls to these functions are generated automatically by the JMacro
quasi-quoters. JMacro
can not automatically add these imports, so you will need to do it by hand if you use explicit import lists. Alternatively, you can just import Language.Javascript.JMacro
without an explicit import list.
> import Control.Applicative ((<$>), optional) > import Control.Monad (msum) > import Control.Monad.State (StateT, evalStateT) > import Control.Monad.Trans (liftIO) > import qualified Data.Map as Map > import Data.Maybe (fromMaybe) > import Happstack.Server (Response, ServerPartT, dir, mapServerPartT, look, nullConf, > ok, simpleHTTP, toResponse) > import Happstack.Server.HSP.HTML (defaultTemplate) -- ^ also imports 'ToMessage XML' > import Happstack.Server.JMacro (jmResponse) -- ^ ToMessage instance for JStat > import HSP ( Attr(..), EmbedAsAttr(..), EmbedAsChild(..) > , genElement, genEElement > ) > import HSP.ServerPartT () -- ^ instance 'XMLGenerator ServerPartT' > import HSX.JMacro ( IntegerSupply(..) > , nextInteger') -- EmbedAsChild & EmbedAsAttr for JStat > import Language.Javascript.JMacro ( ToJExpr(..), Ident(..), JStat(..), JExpr(..) > , JVal(..), jmacro, jsv, jLam, jVarTy) > import System.Random (Random(..)) >
In order to ensure that each <script>
tag generates unique variables
names, we need a source of unique prefixes. An easy way to do that is to
wrap the ServerPartT
monad around a StateT
monad that supplies
integers:
> type JMacroPart = ServerPartT (StateT Integer IO) > > instance IntegerSupply JMacroPart where > nextInteger = nextInteger' >
The nextInteger'
helper function has the type:
> nextInteger' :: (MonadState Integer m) => m Integer
To use JMacroPart with simpleHTTP
, we just evaluate the StateT
monad:
> main :: IO () > main = simpleHTTP nullConf $ flatten handlers > where > flatten :: JMacroPart a -> ServerPartT IO a > flatten = mapServerPartT (flip evalStateT 0) >
<script>
tagNow that we have the scene set, we can actually look at some JMacro usage.
In this example we embed a single JavaScript block inside the page:
> helloJMacro :: JMacroPart Response > helloJMacro = > toResponse <$> defaultTemplate "Hello JMacro" () > <div> > <% [$jmacro| > var helloNode = document.createElement('h1'); > helloNode.appendChild(document.createTextNode("Hello, JMacro!")); > document.body.appendChild(helloNode); > |] %> > </div> >
We do not need to specify the <script>
tag explicitly, it will automatically be created for us.
The syntax [$jmacro| ... |]
is the magic incantation for running the
jmacro
quasiquoter. In GHC 7.x, the $ is no longer required, so in
theory you could write, [jmacro| ... |]
. However, HSX has not been updated to support the $ free syntax. So, for now you will need to stick with the $ syntax, despite the compiler warnings saying, Warning: Deprecated syntax: quasiquotes no longer need a dollar sign: $jmacro.
onclick
, etc)We can also use JMacro inside html attributes, such as onclick
.
> helloAttr :: JMacroPart Response > helloAttr = > toResponse <$> defaultTemplate "Hello Attr" () > <h1 style="cursor:pointer" onclick=[$jmacro| alert("that </tickles>!") |]>Click me!</h1> >
Note that we do not have to worry about escaping the ", < or > in the onclick handler. It is taken care of for us automatically! The code is automatically escaped as:
onclick="alert("that </tickles>!");"
According to the HTML spec
it is invalid for </ to appear anywhere inside the <script>
tag.
The JMacro embedding also takes care of handling </ appearing in string literals. So we can just write this:
> helloEndTag :: JMacroPart Response > helloEndTag = > toResponse <$> defaultTemplate "Hello End Tag" () > <%> > <h1>Tricky End Tag</h1> > <% [$jmacro| alert("this </script> won't mess things up!") |] %> > </%> >
And it will generate:
<script type="text/javascript">alert("this <\/script> won't mess things up!");</script>
So far, using HSP with JMacro looks almost exactly like using HSP with plain-old JavaScript. That's actually pretty exciting. It means that the mental tax for using JMacro over straight JavaScript is very low.
Now let's look at an example of hygienic naming. Let's say we write the following block of JavaScript code:
> clickMe :: JStat > clickMe = > [$jmacro| > > var clickNode = document.createElement('p'); > clickNode.appendChild(document.createTextNode("Click me!")); > document.body.appendChild(clickNode); > var clickCnt = 0; > clickNode.setAttribute('style', 'cursor: pointer'); > clickNode.onclick = function () { clickCnt++; > alert ('Been clicked ' + clickCnt + ' time(s).'); > }; > |] >
That block of code tracks how many times you have clicked on the
Click me! text. It uses a global variable to keep track of
the number of clicks. Normally that would spell trouble. If we tried
to use that code twice on the same page, both copies would end up
writing to the same global variable clickCnt
.
But, JMacro automatically renames the variables for us so that the names are unique. In the following code each Click me! tracks its counts separately:
> clickPart :: JMacroPart Response > clickPart = > toResponse <$> defaultTemplate "Hygienic Naming" () > <div> > <h1>A Demo of Happstack+HSP+JMacro</h1> > <% clickMe %> > <% clickMe %> > </div> >
Of course, sometimes we want the code blocks to share a global variable. We can easily do that by changing the line:
> var clickCnt = 0;
to
> var !clickCnt = 0;
The use of ! when declaring a variable disables hygienic naming. Now all the copies of clickMe2
will share the same counter:
> clickMe2Init :: JStat > clickMe2Init = > [$jmacro| var !clickCnt = 0; |]; > > clickMe2 :: JStat > clickMe2 = > [$jmacro| > > var clickNode = document.createElement('p'); > clickNode.appendChild(document.createTextNode("Click me!")); > document.body.appendChild(clickNode); > clickNode.setAttribute("style", "cursor: pointer"); > clickNode.onclick = function () { clickCnt++; > alert ('Been clicked ' + clickCnt + ' time(s).'); > }; > |] > > clickPart2 :: JMacroPart Response > clickPart2 = > toResponse <$> defaultTemplate "Hygienic Naming" > <% clickMe2Init %> > <div> > <h1>A Demo of Happstack+HSP+JMacro</h1> > <% clickMe2 %> > <% clickMe2 %> > </div> >
Hygienic naming affects function declarations as well. If we want to define a function in <head>
, but call the function from the <body>
, then we need to disable hygienic naming. We can do that using the ! trick again:
> function !hello(noun) { alert('hello ' + noun); }
JMacro also has some syntax extensions for declaring functions. We can create an anonymous function using Haskell-like syntax assign it to a variable:
> var !helloAgain = \noun ->alert('hello again, ' + noun);
Another option is to use the ML-like fun
keyword to declare a function. When using fun
we do not need the !.
> fun goodbye noun { alert('goodbye ' + noun); }
Or we can do both:
> fun goodbyeAgain noun -> alert('goodbye again, ' + noun);
Here they all are in an example:
> functionNames :: JMacroPart Response > functionNames = > toResponse <$> defaultTemplate "Function Names" > <% [$jmacro| > function !hello(noun) { alert('hello, ' + noun); } > var !helloAgain = \noun ->alert('hello again, ' + noun); > fun goodbye noun { alert('goodbye ' + noun); } > fun goodbyeAgain noun -> alert('goodbye again, ' + noun); > |] > %> > <%> > <button onclick=[$jmacro| hello('world'); |]>hello</button> > <button onclick=[$jmacro| helloAgain('world'); |]>helloAgain</button> > <button onclick=[$jmacro| goodbye('world'); |]>goodbye</button> > <button onclick=[$jmacro| goodbyeAgain('world'); |]>goodbyeAgain</button> > </%> >
We can also splice Haskell values into the JavaScript code by using `( )`
. In the following example, the onclick
action for the <button>
calls revealFortune()
. The argument to revealForture
is the String
returned by evaluating the Haskell expression fortunes !! n
.
> fortunePart :: JMacroPart Response > fortunePart = > do let fortunes = > ["You will be cursed to write Java for the rest of your days." > , "Fortune smiles upon you, your future will be filled with lambdas" > ] > n <- liftIO $ randomRIO (0, (length fortunes) - 1) > > toResponse <$> defaultTemplate "Fortune" > <% [$jmacro| > fun revealFortune fortune > { > var b = document.getElementById("button"); > b.setAttribute('disabled', 'disabled'); > var p = document.getElementById("fortune"); > p.appendChild(document.createTextNode(fortune)); > } > |] > %> > <div> > <h1>Your Fortune</h1> > <p id="fortune"></p> > <button id="button" onclick=[$jmacro| revealFortune(`(fortunes !! n)`); |]> > Click to reveal your fortune > </button> > </div> >
ToJExpr
to convert Haskell values to JavaScriptJMacro can embed common types such as Int
, Bool
, Char
, String
, etc, by default. But we can also embed other types by creating a ToJExpr
instance for them. For example, let's say we create some types for reporting the weather:
> data Skies = Cloudy | Clear > deriving (Bounded, Enum, Eq, Ord, Read, Show) > > newtype Fahrenheit = Fahrenheit Double > deriving (Num, Enum, Eq, Ord, Read, Show, ToJExpr, Random) > > data Weather = Weather > { skies :: Skies > , temp :: Fahrenheit > } > deriving (Eq, Ord, Read, Show) > > instance Random Skies where > randomR (lo, hi) g = > case randomR (fromEnum lo, fromEnum hi) g of > (c, g') -> (toEnum c, g') > random g = randomR (minBound, maxBound) g > > instance Random Weather where > randomR (Weather skiesLo tempLo, Weather skiesHi tempHi) g = > let (skies, g') = randomR (skiesLo, skiesHi) g > (temp, g'') = randomR (tempLo, tempHi) g' > in ((Weather skies temp), g'') > random g = > let (skies, g') = random g > (temp, g'') = random g' > in ((Weather skies temp), g'') >
To pass these values into the generated JavaScript, we simply create a ToJExpr
instance:
> class ToJExpr a where > toJExpr :: a -> JExpr
For Fahrenheit
, we were actually able to derive the ToJExpr
instance automatically (aka, deriving (ToJExpr)
), because it is a newtype
wrapper around Double
which already has a ToExpr
instance.
For Skies
, we can just convert the constructors into JavaScript strings:
> instance ToJExpr Skies where > toJExpr = toJExpr . show >
For the Weather
type, we create a JavaScript object/hash/associative array/record/whatever you want to call it:
> instance ToJExpr Weather where > toJExpr (Weather skies temp) = > toJExpr (Map.fromList [ ("skies", toJExpr skies) > , ("temp", toJExpr temp) > ]) >
Now we can splice a random weather report into our JavaScript:
> weatherPart :: JMacroPart Response > weatherPart = > do weather <- liftIO $ randomRIO ((Weather minBound (-40)), (Weather maxBound 100)) > toResponse <$> defaultTemplate "Weather Report" () > <div> > <% [$jmacro| > var w = `(weather)`; > var p = document.createElement('p'); > p.appendChild(document.createTextNode("The skies will be " + w.skies + > " and the temperature will be " + > w.temp.toFixed(1) + "°F")); > document.body.appendChild(p); > |] %> > </div> >
ToJExpr
has an instance for JSValue
from the json library. So, if your type already has a JSON
istance, you can trivially create a ToJExpr
instance for it:
> instance ToJExpr Foo where > toJExpr = toJExpr . showJSON
So far we have used JMacro to generate JavaScript that is embedded in HTML. We can also use it to create standalone JavaScript.
First we have a script template that is parametrized by a greeting.
> externalJs :: String -> JStat > externalJs greeting = > [$jmacro| > window.greet = function (noun) > { > alert(`(greeting)` + ' ' + noun); > } > |] >
Notice that we attached the greet
function to the window
. The ToMessage
instance for JStat
wraps the Javascript in an anonymous function to ensure that statements execute in a local scope. That helps prevents namespace collisions between different external scripts. But, it also means that top-level unhygienic variables will not be global available. So we need to attach them to the window
.
Next we have a server part with two sub-parts:
> externalPart :: JMacroPart Response > externalPart = dir "external" $ msum [
If external/script.js is requested, then we check for a query string parameter greeting
and generate the script. toResponse
will automatically convert the script to a Response
and serve it with the content-type, text/javascript; charset=UTF-8
:
> dir "script.js" $ > do greeting <- optional $ look "greeting" > ok $ toResponse $ externalJs (fromMaybe "hello" greeting)
Next we have an html page that includes the external script, and calls the greet
function:
> , toResponse <$> defaultTemplate "external" > <script type="text/javascript" src="/external/script.js?greeting=Ahoy" /> > <div> > <h1>Greetings</h1> > <button onclick=[$jmacro| greet('JMacro'); |]>Click for a greeting.</button> > </div> > ] >
Instead of attaching the greet
function to the window
, we could instead use jmResponse
to serve the JStat
. jmResponse
does not wrap the Javascript in an anonymous function so the window
work-around is not needed. We do need to use !
to make sure the name of the greet2
function is not mangled though:
> externalJs2 :: String -> JStat > externalJs2 greeting = > [$jmacro| > function !greet2 (noun) > { > alert(`(greeting)` + ' ' + noun); > } > |] > > > externalPart2 :: JMacroPart Response > externalPart2 = dir "external2" $ msum > [ dir "script.js" $ > do greeting <- optional $ look "greeting" > jmResponse $ externalJs2 (fromMaybe "hello" greeting) > > , toResponse <$> defaultTemplate "external 2" > <script type="text/javascript" src="/external2/script.js?greeting=Ahoy" /> > <div> > <h1>Greetings</h1> > <button onclick=[$jmacro| greet2('JMacro'); |]>Click for a greeting.</button> > </div> > ] >
Here is a little page that links to all the JMacro demos:
> demosPart :: JMacroPart Response > demosPart = > toResponse <$> defaultTemplate "demos" () > <ul> > <li><a href="/hello" >Hello, JMacro</a></li> > <li><a href="/attr" >Hello, Attr</a></li> > <li><a href="/endTag" >Hello, End Tag</a></li> > <li><a href="/clickMe" >ClickMe</a></li> > <li><a href="/clickMe2" >ClickMe2</a></li> > <li><a href="/functions">Function Names</a></li> > <li><a href="/fortune" >Fortune</a></li> > <li><a href="/weather" >Weather</a></li> > <li><a href="/external" >External</a></li> > <li><a href="/external2" >External 2</a></li> > </ul> >
and our routes:
> handlers :: JMacroPart Response > handlers = > msum [ dir "hello" $ helloJMacro > , dir "attr" $ helloAttr > , dir "endTag" $ helloEndTag > , dir "clickMe" $ clickPart > , dir "clickMe2" $ clickPart2 > , dir "functions" $ functionNames > , dir "fortune" $ fortunePart > , dir "weather" $ weatherPart > , externalPart > , externalPart2 > , demosPart > ] >
[Source code for these demos is here.]
IntegerSupply
instanceIf you do not like having to use the StateT
monad transformer to
generate names, there are other options. For example, we could use
Data.Unique
to generate unique names:
> instance IntegerSupply (ServerPartT IO) where > nextInteger = fmap (fromIntegral . (`mod` 1024) . hashUnique) (liftIO newUnique)
This should be safe as long as you have less than 1024 different JMacro blocks on a single page.
For more information on using JMacro I recommend reading this wiki page and the tutorial at the top of Language.Javascript.JMacro. The documentation is this tutorial has covered the basics of JMacro, but not everything!