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):
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.
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:
#ifdef HsColour
> nextInteger' :: (MonadState Integer m) => m Integer
#endif
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)
>
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.
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:
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:
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:
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:
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:
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));
> }
> |]
> %>
>
JMacro 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:
#ifdef HsColour
> class ToJExpr a where
> toJExpr :: a -> JExpr
#endif
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:
> <% [$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);
> |] %>
>
>
`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:
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.
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:
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"
>
>
>
Greetings
>
>
> ]
>
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:
If 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:
This should be safe as long as you have less than 1024 different JMacro blocks on a single page.
More Information
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!