Manual

Liquid Epsilon is a smorgasbord of modules for building single-page, client-side web and mobile applications, using Haskell and the UHC compiler.

Overview

This project is based on the Utrecht Haskell Compiler's JavaScript backend. UHC-JS compiles a Haskell program to JavaScript and automatically generates the boilerplate HTML and runtime libraries required to execute the program in a web browser. UHC-JS also provides a foreign function interface (FFI) which facilitates low-level interaction with JavaScript code and the underlying browser environment. The backend was originally implemented by Atze Dijkstra and released with UHC version 1.1. The UHC compiler supports almost all Haskell98 features, plus experimental extensions and runs under Mac OS X, Windows (cygwin), and various Unix flavors.

Installation

  1. For instructions on how to install UHC-JS, see this page.

  2. Once UHC is installed, clone the repository to a suitable location. Make sure the Util namespace is available to the compiler. (Simple solution: Run uhc from the folder directly above Util)

Other sources of information

I should also mention that the resource which really helped me getting started was a research paper named “Improving the UHC JavaScript backend”, by Jurriën Stutterheim. A report that has made several enhancements to the UHC-JS libraries.

From the paper:

The primary contribution of this report is a whole new way of dealing with JavaScript objects from the Haskell world. To support this, we implement several new primitive functions and expand the foreign function interface expression language. In addition, we implement foreign wrappers and dynamic imports.

Objectives

The goal of the project is to show the feasibility of developing client-side web applications using Haskell, and provide practical tools for doing so. Still, it leaves a lot to be desired in terms of performance and scalability.

Why not simply use the original libraries?

This project reinvents a lot of wheels; in particular the Aeson and Blaze libraries. Why not use them instead?

Answer: That would be great! However, due to tricky dependencies (Data.ByteString etc.), getting these to compile with UHC and the -tjs option is not always straightforward.

  • blaze-html is © Jasper Van der Jeugt, Simon Meier
  • Aeson is © 2011, 2012, 2013 Bryan O'Sullivan © 2011 MailRank, Inc

Hello world

module Main where

import Util.Base

main :: IO ()
main = do
    wrap $ do
        print "Hello world!"
    >>= onReady 

To compile a program, a single command is used. If your source file is named Main.hs, simply type:

uhc Main.hs -tjs 

This generates a main.html file. Open this document in your browser and you should see a blank page with the text “Hello world!”. (You don't say!)

A closer look at Hello world

The wrap>>= onReady block serves a similar purpose to the $(function(){ ... }); construct in jQuery. That is, the inner do-block is guaranteed to run strictly after the document.DOMContentLoaded event has occurred.

Note: Ideally, waiting for the document to be “ready”, should be implemented in the runtime rather than explicitly in application code.

Wrapping functions for JavaScript import

The wrap function creates a FunPtr (IO a) function pointer from an IO a action. This is needed by onReady to allow interaction with the JavaScript environment.

wrap :: IO a -> IO (FunPtr (IO a)) 

This is further explained in Jurriën Stutterheim's paper:

A wrapper import allows a Haskell function to be wrapped as a foreign function at runtime. The Haskell 2010 report states that a wrapper must be of the form ft -> IO (FunPtr ft). This allows a Haskell function to be used as callback from a JavaScript function.

The type of onReady is:

onReady :: FunPtr (IO a) -> IO () 

Here is another way of writing the above “Hello world” example, although I prefer the former version.

myMain :: IO ()
myMain = print "Hello world!"

main :: IO ()
main = do
    f <- wrap myMain
    onReady f 

Important “gotcha” about external libraries

Some modules (in particular Util.Handlebars and Util.Storage) require additional JavaScript plugins to work. Util.Base exports addScript and addScripts, used to dynamically add such external scripts to your document. These functions create a new <script> element at run-time, assign it the appropriate attributes, and append it to the document.

main :: IO ()
main = do
    addScript "../../js/handlebars.js"
    wrap $ do
        ...
    >>= onLoad  -- We must use onLoad instead of onReady to wait for
                -- the added scripts to load. 

Note the use of onLoad instead of onReady in the above example.

addScripts accepts a list of files:

addScripts [ "../../js/handlebars.js"
           , "../../js/jstorage.js"
           ] 

Needless to say, you should verify that the plugins are present and readable in your file system.


DOM

import Util.DOM 
Think of this module as a thin wrapper for the more common JavaScript DOM access functions, such as document.getElementById() and document.write().

The Element type is used throughout this and other modules to represent a DOM element. Element is an opaque pointer that cannot be modified or otherwise dereferenced outside of JavaScript code.

data ElementPtr
type Element = Ptr ElementPtr 

Most of the functions exported here should be reasonably self-explanatory. Otherwise, the following examples will give you some idea of how the API works. Although this approach to DOM manipulation lacks the declarative expressiveness one rightfully might expect from a Haskell library, these “low-level” functions are helpful for raw DOM access when writing library code.

Here is a simple function that creates a <button> element:

createButton :: String -> IO Element
createButton name = do
    e <- createElement "button"
    t <- createTextNode name
    t ~> e
    return e 

The ~> operator is an infix version of appendChild with the arguments flipped.

-- | Add an element after the last child node of the specified element.
appendChild :: Element   -- ^ Parent node
            -> Element   -- ^ The child element 
            -> IO () 

Now to add the button to our document:

-- Create a standard button and append it to the document's body element.
addHelloButton :: IO Element
addHelloButton = do
    e <- createButton "Hello!"
    body <- documentBody
    e ~> body 
    return e 

getElementById returns a IO (Maybe Element) to properly handle cases where no element is found.

getElementById :: String -> IO (Maybe Element) 

A more elaborate example

This example also uses setInnerHtml, onClick and some other crazy stuff.

module Main where

import Util.Base
import Util.DOM
import Util.String     ( pack )

sayHello :: IO ()
sayHello = do
    body <- documentBody
    e <- createTextNode "Hello smorgosbord!"
    p <- createElement "p"
    e ~> p
    p ~> body 

createButton :: String -> IO Element
createButton name = do
    e <- createElement "button"
    t <- createTextNode name
    t ~> e
    return e

-- Create a standard button and append it to 
-- the document's body element.
addHelloButton :: IO Element
addHelloButton = do
    e <- createButton "Hello!"
    body <- documentBody
    appendChild body e
    return e

addBorder :: IO ()
addBorder = do
    el <- getElementById "my-div"
    -- getElementById returns a Maybe Element
    case el of
        Nothing -> return ()
        Just e  -> do
            setAttribute e "style" 
                           "border: 2px solid red"

main :: IO ()
main = do
    wrap $ do
        e <- addHelloButton
        e `onClick` sayHello
        -- Create a simple div element
        div <- createElement "div"
        setInnerHtml div "This is a div"
        setAttribute div "id" "my-div"
        body <- documentBody
        div ~> body   -- i.e., appendChild body div
        addBorder

    >>= onReady 

String

import Util.String 
Util.String exports the pack and unpack functions, for conversion between Haskell Strings and plain JavaScript string format.

An opaque PackedString pointer type is used throughout the lower-level JavaScript API, and expected by most FFI function calls. Use pack to convert a Haskell String to a PackedString and unpack for the other way around.

pack :: String -> PackedString
unpack :: PackedString -> String 

Keep in mind that interchanging between one string representation form and the other is an expensive operation. It is also well-known that relying on Haskell's default lazy list implementation for text data, even when compiling to native code, is inefficient.

Many functions that accept String arguments also have alternative PackedString versions. By convention, their names end with a ' character:

-- | Create an element with the specified name.
createElement :: String -> IO Element
createElement' :: PackedString -> IO Element 

Prefer processing text in packed form whenever this is possible.


HTML

import Util.HTML
import Util.HTML.Attributes
import Util.HTML.Elements 
Modeled after the Blaze library (without being as blazingly fast unfortunately), the Util.HTML module makes it possible to embed HTML templates in Haskell code.

Why not simply use the original libraries? Please refer to to the top of this document.

Here is an example to show the rationale behind the HTML combinator approach:

myHtml :: Html
myHtml = body $ do
             p $ 
                 a ! href "#" $< "This is a link"
             p $
                 span $< "Calcudoku!" 

The above snippet translates into the following block of HTML:

<body>
    <p>
        <a href="#">This is a link</a>
    </p>
    <p>
        <span>Calcudoku!</span>
    </p>
</body> 

Util.HTML comes with a set of standard HTML elements and attributes, found under Util.HTML.Elements and Util.HTML.Attributes respectively. Typically, you import these modules qualified to avoid namespace issues. The documentation for Blaze recommends importing everything twice – once with an alias and once without. We will follow the same convention here.

import Util.HTML.Attributes
import Util.HTML.Elements
import qualified Util.HTML.Attributes   as A
import qualified Util.HTML.Elements     as H 

Now we can start building some HTML. Here is a small example, adapted from the Blaze documentation:

module Main where

import Control.Monad             ( forM_ )
import Util.Base
import Util.DOM                  ( documentWrite )
import Util.HTML
import Util.HTML.Attributes
import Util.HTML.Elements

import qualified Util.HTML.Attributes   as A
import qualified Util.HTML.Elements     as H

numbers :: Int -> Html
numbers n = 
    docTypeHtml $ do
        H.head $ H.title $< "Natural numbers"
        body $ do
            p $< "A list of natural numbers:"
            ul $ forM_ [1 .. n] $ li . toHtml . show

main :: IO ()
main = do
    wrap $ do
        documentWrite $ renderHtml $ numbers 42

    >>= onReady 

Html is a type synonym for HtmlM (). The monadic behavior of HtmlM makes it straightforward to line up your elements inside the do-blocks, in the familiar, sequential manner.

Rendering

To eventually use the HTML in our document, renderHtml converts a Html value to an ordinary String, with special characters converted to their correct HTML entities.

renderHtml :: Html -> String 

Leafs and parent nodes

There are two types of elements, leafs and parents. Leafs are good to go on their own:

br :: Html 

A parent (<div>, <p>, <body>, etc.) takes another Html child node and encapsulates it:

div :: Html -> Html 

The $< operator is shorthand for the toHtml function. It creates a simple text node.

($<) :: (Html -> Html) -> String -> Html
($<) a = a . toHtml 

That is, instead of H.title $ toHtml "foo"; we can write H.title $< "foo".

Attributes

Attributes can be added to both leafs and parents using the ! infix operator:

a ! href "http://www.google.com" $< "A link" 

Naturally, you can attach as many attributes as you like.

img ! src "foo.png" ! alt "A foo image" 

Examples, examples, examples! Here are some more examples.

markup :: Html
markup = do
    p $ do 
        a ! href "#" $< "A link"
    p $ do
        b $< "Some bold text"
    p $ do
        ul $ do
            li $< "item"
            li $< "item"
            li $< "item"
    p $ do
        H.div ! style "border: 2px solid orange" 
            $< "Have a nice div..." 

PackedString

The entire Util.HTML library is also available in a PackedString version under the Util.HTML.PS namespace.

import Util.HTML.PS
import Util.HTML.PS.Attributes
import Util.HTML.PS.Elements

import qualified Util.HTML.PS.Attributes   as A
import qualified Util.HTML.PS.Elements     as H 

JSON

import Util.JSON 
Mimicking the Aeson library, the JSON module allows nested parsing and serialization for easy, type-safe translation of Haskell data into native JavaScript objects.

Why not simply use the original libraries? Please refer to the top of this document.

Just like Aeson, the Util.JSON module defines two type classes; FromJSON and ToJSON. Instantiate these to make your data types available to the various APIs. Instances for many of the primitive types are already defined in Util.JSON.

Using toStr and fromStr

toStr translates any ToJSON data to a String value.

toStr :: (ToJSON a) => a -> String 

For instance:

example :: String
example = toStr [1,2,3,4]    -- "[1,2,3,4]" 

fromStr does the opposite:

fromStr :: (FromJSON a) => String -> Maybe a 

Note that fromStr returns a Maybe a, since parsing may fail.

example :: Maybe [Int]
example = fromStr "[1,2,3,4]" 

Behind the scenes

The key behind safe translation between JSON and Haskell data is the intermediate Value type, used internally by the Parser monad.

-- | JSON value represented as a Haskell type.
data Value = Object !Object
           | Array  ![Value]
           | Str    !String
           | Number !Double
           | Bool   !Bool
           | Null 

Rolling your own instances

From JSON

Creating a FromJSON instance is, again, very similar to how Aeson works. The type class is defined as:

class FromJSON a where
    fromJSON :: Value -> Maybe a 

For this example, let us consider a data type…

data Person = Person
    { name       :: String
    , age        :: Int
    , occupation :: Occupation
    } 

A compatible JSON object could be constructed as:

{ 
  "name": "Bob",
  "age": 32,
  "occupation": "manager"
} 

We use applicative style to lift the Person constructor to Maybe. Recall that the type signature for fromJSON is Value -> Maybe a.

instance FromJSON Person where
    fromJSON (Object v) =
        Person <$> v .! "name"
               <*> v .! "age"
               <*> v .! "occupation"
    fromJSON _ = Nothing 

The .! operator looks up each key in the Object association list and then calls fromJSON recursively if a value is found which matches the key.

(.!) :: (FromJSON a) => Object -> String -> Maybe a 

The occupation field holds another custom data type.

data Occupation = Chef | Pilot | Manager 

For simple algebraic types such as this, we use pattern matching.

instance FromJSON Occupation where
    fromJSON (Str "chef")    = Just Chef
    fromJSON (Str "pilot")   = Just Pilot
    fromJSON (Str "manager") = Just Manager
    fromJSON _ = Nothing 

To JSON

Here is how the ToJSON type class is defined:

class ToJSON a where
    toJSON :: a -> Value 

We use the obj function and the |= operator to build up a Value from our Person record:

instance ToJSON Person where
    toJSON (Person n a o) =
        obj [ "name"       |= n
            , "age"        |= a
            , "occupation" |= o 
            ] 

|= is defined in Util.JSON:

(|=) :: ToJSON a => String -> a -> (String, Value) 

For our Occupation type, we simply translate each constructor to a string literal, since JSON doesn't have an enumerated type.

instance ToJSON Occupation where
    toJSON Chef    = Str "chef"
    toJSON Pilot   = Str "pilot"
    toJSON Manager = Str "manager" 

Finally, we use the mapFrom and mapTo helpers to automatically extend our instances to lists:

instance FromJSON [Person] where
    fromJSON = mapFrom
instance ToJSON [Person] where
    toJSON = mapTo 

Let's run some tests on our new instances.

main :: IO ()
main = do
    wrap $ do
        case fromStr "[1,2,3]" :: Maybe [Int] of
          Nothing -> putStrLn "--"
          Just a  -> putStrLn $ show a ++ "<br><br>"

        case fromStr "[true]" :: Maybe [Bool] of
          Nothing -> putStrLn "--"
          Just b  -> putStrLn $ show b ++ "<br><br>"

        case fromStr "{\"name\":\"Mr. Phelps\",\"age\":55,\"occupation\":\"manager\"}" :: Maybe Person of
          Nothing -> putStrLn "--"
          Just c  -> putStrLn $ show (name c) ++ "<br><br>"

        case fromStr "[1,3,4]" :: Maybe Person of
          Nothing -> putStrLn "Not a valid Person object.<br><br>"
          Just d  -> putStrLn $ show (name d) ++ "<br><br>"

        let s = toStr $ Person { name       = "Mr. Phelps"
                               , age        = 55
                               , occupation = Manager
                               }
        putStrLn s

    >>= onReady 

This should output:

[1,2,3]
[True]
"Mr. Phelps"
Not a valid Person object.
{"name":"Mr. Phelps","age":55,"occupation":"manager"} 

Handlebars

import Util.Handlebars 
The Handlebars module is a wrapper for the JavaScript library with the same name.

According to the Handlebars documentation:

Handlebars provides the power necessary to let you build semantic templates effectively with no frustration.

Handlebars.js is copyright © 2011 Yehuda Katz, licensed under the MIT license and can be downloaded from here.

JS dependency

To use this module, you need to make sure that the handlebars.js plugin is available and included in your HTML file. The easiest way to achieve this is by adding a <script> tag dynamically to your document with the help of the addScript function from Util.Base.

main :: IO ()
main = do
    addScript "../../js/handlebars.js"
    wrap $ do
        ...
    >>= onLoad  -- We must use onLoad instead of onReady to wait for
                -- the added scripts to load. 

Handlebars expressions

Handlebars templates look like regular HTML, with embedded expressions – variable names surrounded by double curly brackets. Here is the template we will use in the following examples:

<h1>{{title}}</h1>
<p>Author: {{author}}</p> 

The idea is to replace these placeholders with actual data, represented as an ordinary record type in Haskell:

data Book = Book 
    { title  :: String
    , author :: String 
    }

instance ToJSON Book where
    toJSON (Book t a) = obj [ "title"  |= t 
                            , "author" |= a
                            ] 

Util.Handlebars works closely with the JSON module to allow safe and seamless translation between JavaScript objects and Haskell data.

Compiling a template yields a template function Ptr a -> IO PackedString.

compile :: forall a. String -> IO (Ptr a -> IO PackedString) 

render accepts a function of this type together with a ToJSON instance, from which it generates the final output.

render :: (ToJSON a) => (Ptr a -> IO PackedString) -> a -> IO String 

This is what the flow normally looks like:

t   <- compile "<p>{{hello}}</p>"  -- template
out <- render t $ obj              -- some ToJSON instance
print out 

Putting all of this together into a runnable program…

module Main where

import Util.Base
import Util.DOM
import Util.Handlebars

template :: String
template = "<h1>{{title}}</h1><p>Author: <b>{{author}}</b></p>"

data Book = Book 
    { title  :: String
    , author :: String }

instance ToJSON Book where
    toJSON (Book t a) = obj [ "title"  |= t 
                            , "author" |= a
                            ]  

main :: IO ()
main = do
    addScript "../../js/handlebars.js"
    wrap $ do
        t <- compile template
        out <- render t $ Book "The Adventures of Tom Sawyer" "Mark Twain"
        documentWrite out

    >>= onLoad  

Collections

Handlebars provides an #each helper, used to iterate over collections of data.

<ul class="people">
  {{#each people}}
  <li>{{name}}</li>
  {{/each}}
</ul> 

For this to work, we need to “wrap” our Haskell list in a collection record, and provide a ToJSON instance for [Book]. Util.Handlebars defines a Collection newtype for this purpose:

newtype Collection a = Collection [a] 

We may now introduce a simple type synonym for our collection of Books.

type BookCollection = Collection Book

instance ToJSON [Book] where
    toJSON = mapTo

instance ToJSON BookCollection where
    toJSON (Collection b) =
        obj [ "books" |= b ] 

Here is a complete, runnable program:

module Main where

import Util.Base
import Util.DOM
import Util.Handlebars

template :: String
template = "<ul>\
           \{{#each books}}\
           \<li>Title: <b>{{title}}</b>&nbsp;\
           \Author: {{author}}\
           \{{/each}}\
           \</ul>"

data Book = Book 
    { title  :: String
    , author :: String }

instance ToJSON Book where
    toJSON (Book t a) = obj [ "title"  |= t 
                            , "author" |= a
                            ]  

type BookCollection = Collection Book

instance ToJSON [Book] where
    toJSON = mapTo

instance ToJSON BookCollection where
    toJSON (Collection b) =
        obj [ "books" |= b ]

main :: IO ()
main = do
    addScript "../../js/handlebars.js"
    wrap $ do
        t <- compile template
        out <- render t $ Collection
            [ Book "A Tale of Two Cities" "Charles Dickens"
            , Book "War and Peace"        "Leo Tolstoy"          
            , Book "The Godfather"        "Mario Puzo"
            , Book "Catch-22"             "Joseph Heller"
            ]
        documentWrite out

    >>= onLoad 

Handlebars HTML

import Util.HTML.Handlebars 
This module extends the functionality of Util.Handlebars to include support for using Util.HTML combinators to build templates.

Let's adjust our earlier example to see how this works.

The Book template in our first example could now be written as:

template :: Html
template = do
    h1 $< "Book"
    p $ do
        H.span $< "Title:"     >> nbsp
        b      $< "{{title}}"  >> nbsp
        H.span $< "Author:"    >> nbsp
        H.span $< "{{author}}" 

Util.Handlebars.HTML also defines an $? operator for Handlebars expressions, making it possible to write b $? "title" instead of b $< "{{title}}". This is, of course, completely optional.

For our collection, we use the each function.

each :: String -> Html -> Html 

This is how we use each:

collectionTempl :: Html
collectionTempl = do
    h1 $< "List of books"
    ul $ each "books" $
        li $ do
            H.span $< "Title:"     >> nbsp
            b      $? "title"      >> nbsp
            H.span $< "Author:"    >> nbsp
            H.span $? "author" 

To render a template, we now use renderWithHtml, defined in Util.HTML.Handlebars.

renderWithHtml :: (ToJSON a) => Html -> a -> IO String 

For instance,

out <- renderWithHtml template $ Book "Harry Potter" "J.K. Rowling"
documentWrite out 

Finally, here is everything again as a complete, runnable program.

module Main where

import Util.Base
import Util.DOM
import Util.HTML
import Util.HTML.Attributes
import Util.HTML.Elements
import Util.HTML.Handlebars
import Util.Handlebars

import qualified Util.HTML.Attributes   as A
import qualified Util.HTML.Elements     as H

template :: Html
template = do
    h1 $< "Book"
    p $ do
        H.span $< "Title:"     >> nbsp
        b      $? "title"      >> nbsp
        H.span $< "Author:"    >> nbsp
        H.span $? "author"

collectionTempl :: Html
collectionTempl = do
    h1 $< "List of books"
    ul $ each "books" $
        li $ do
            H.span $< "Title:"     >> nbsp
            b      $? "title"      >> nbsp
            H.span $< "Author:"    >> nbsp
            H.span $? "author"

data Book = Book 
    { title  :: String
    , author :: String }

instance ToJSON Book where
    toJSON (Book t a) = obj [ "title"  |= t 
                            , "author" |= a
                            ]  

type BookCollection = Collection Book

instance ToJSON [Book] where
    toJSON = mapTo

instance ToJSON BookCollection where
    toJSON (Collection b) =
        obj [ "books" |= b ]

main :: IO ()
main = do
    addScript "../../js/handlebars.js"
    wrap $ do
        out <- renderWithHtml template $ 
            Book "The Adventures of Tom Sawyer" "Mark Twain"
        out' <- renderWithHtml collectionTempl $ 
            Collection [ Book "A Tale of Two Cities" "Charles Dickens"
                       , Book "War and Peace"        "Leo Tolstoy"          
                       , Book "The Godfather"        "Mario Puzo"
                       , Book "Catch-22"             "Joseph Heller"
                       ]
        documentWrite $ out ++ out'

    >>= onLoad  

FRP

import Util.FRP 
Functional reactive programming is, in my opinion, the most elegant approach there is to UI programming. Pioneered by Conal Elliott, this highly declarative programming style aims to solve to the problem of modeling data that varies over time.

The FRP module is still at an early stage. Influenced in particular by Evan Czaplicki's Elm language (elm-lang), it centers around the notion of a Signal.

The Signal type constructor

If we consider a type a, we may think of a value of type Signal a as an a-value which exists in a time domain. For instance, Util.FRP provides a signal for the vertical position of the window scrollbar:

-- | Window scroll Y position signal.
scrollYSignal :: Signal Int 

The beauty of FRP lies in the ability to take any function and lift it (Signal is an applicative functor) to one that operates on signals, thereby achieving the desired interactive behavior of UI elements without the need for callbacks or similar artifacts.

Hello FRP world

Let's start with a non-reactive counterexample:

module Main where

import Util.Base
import Util.DOM

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        -- Create two input fields:
        e1 <- createElement "input"
        e1 ~> body
        e2 <- createElement "input"
        e2 ~> body
        -- Set the 'value' property of the second input.
        setValue e2 "Hello static world!"

    >>= onReady 

This is pretty standard stuff. We just set the value of the second input to “Hello static world!” and that's it. Moving forward, we note that the function…

inputValue :: Element -> Signal String 

… is used to obtain a signal for the value proprety of an input field. However, to apply a Signal String to setValue, we must first lift setValue e2 from,

String -> IO () to Signal String -> Signal IO ().

Our friend fmap takes care of that:

fmap $ setValue e2 

After applying the inputValue signal from the first input, we now end up with a Signal IO () value.

Similar to the IO monad, we normally don't “escape” out of the Signal functor. The exception is attach, which is used to attach a signal to our document.

attach :: Signal (IO a) -> IO () 

Given this, we can now put everything together:

You can also run this example directly in the browser.
module Main where

import Util.Base
import Util.DOM
import Util.FRP

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        -- Create two input fields:
        e1 <- createElement "input"
        e1 ~> body
        e2 <- createElement "input"
        e2 ~> body
        attach $ fmap (setValue e2) $ inputValue e1

    >>= onReady 

The second input field will now update automatically whenever the first field changes.

Since Signal is an Applicative, we could instead have chosen this option instead of fmap:

attach $ setValue <$> pure e2 
                  <*> inputValue e1 

Creating signals with _bindSignal .

_bindSignal creates a signal by binding an event to some Element property .

_bindSignal :: String      -- ^ The name of the event, e.g., 'change'
            -> String      -- ^ Property to read from when the event is triggered.
            -> Element     -- ^ A HTML DOM element               
            -> Signal a 

_bindSignal is not intended to be used directly in client code, hence the underscore character in the name. When implementing your own signals you should always qualify the return type:

scrollY :: Element -> Signal Int
scrollY = _bindSignal "scroll" "scrollY" 

The above creates a signal associated with the scroll event and the scrollY property of some Element. We will use this to read the scrollbar position of the browser window and delegate this value to an input field.

Since setValue accepts a String, we need to throw in a show to get a function Int -> IO, hence the setValue input . show:

module Main where

import Util.Base
import Util.FRP
import Util.DOM

scrollY :: Element -> Signal Int
scrollY = _bindSignal "scroll" "scrollY"

main :: IO ()
main = do
    wrap $ do
        input <- _init
        win <- window
        attach $ fmap (setValue input . show) $ scrollY win

    >>= onReady

_init :: IO Element
_init = do
    body <- documentBody
    input <- createElement "input"
    setAttribute input "style" "position:fixed; top:0;"
    input ~> body
    div <- createElement "div"
    setInnerHtml div $ concat $ take 30 $ repeat "<p>Hello</p>"
    div ~> body
    return input 

The unit signal and _bindUnitSignal

_bindUnitSignal creates a signal of unit type, i.e., one which is not associated with a property, but instead emits a simple () value.

_bindUnitSignal :: String      -- ^ The name of the event, e.g., 'click'
                -> Element     -- ^ A HTML DOM element
                -> Signal () 

A typical example is a a click event on a DOM element. Util.FRP defines a function clickSignal for this purpose:

clickSignal :: Element -> Signal ()
clickSignal = _bindUnitSignal "click" 

Event forwarding with _bindSignalOn

Using _bindSignalOn we can bind an event associated with one element to a property on some other element. _bindSignalOn is very similar to _bindSignal, only that it takes an extra argument for the element with the property from which to read the value.

-- | Create a signal by binding an event associated with one element 
-- to a property on another element.
_bindSignalOn :: String      -- ^ The name of the event, e.g., 'change'
              -> String      -- ^ Property to read from when the event is triggered.
              -> Element     -- ^ The event element 
              -> Element     -- ^ The property element
              -> Signal a 

In this example, we will use _bindSignalOn to – again, create a signal associated with the window scrollbar position – but this time, one that only emits when a button click event occurs.

module Main where

import Util.Base
import Util.DOM
import Util.FRP

scrollYOnClick :: Element -> IO (Signal Int)
scrollYOnClick b = window >>= return . _bindSignalOn "click" "scrollY" b 

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        win <- window

        input <- createElement "input"
        setAttribute input "style" "position:fixed; top:0;"
        input ~> body

        btn <- createElement "button"
        setAttribute btn "style" "position:fixed; top:30px;"
        setInnerHtml btn "Bork!"
        btn ~> body

        div <- createElement "div"
        setInnerHtml div $ concat $ take 30 $ repeat "<p>Hello</p>"
        div ~> body

        signal <- scrollYOnClick btn
        attach $ fmap (setValue input . show) $ signal

    >>= onReady 

More examples

Moving a div after mouse cursor

module Main where

import Util.Base
import Util.DOM
import Util.FRP

setPos :: Element -> (Int, Int) -> IO ()
setPos e (x, y) = do
    setStyleProperty e "left" $ show x ++ "px"
    setStyleProperty e "top"  $ show y ++ "px"

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        div <- createElement "div"
        setAttribute div "style" "border:1px solid red; width:100px; height:50px; position:absolute;"
        div ~> body
        attach $ fmap (setPos div) mouseSignal

    >>= onReady 

Form validation

You can also run this example directly in the browser.
module Main where

import Control.Monad                  ( msum )
import Util.Base
import Util.DOM
import Util.FRP

type Validator = String -> Maybe String

nonEmpty :: String -> Maybe String
nonEmpty "" = Just "This field must not be empty."
nonEmpty _  = Nothing

numeric :: String -> Maybe String
numeric s = case f s of
              True  -> Nothing
              False -> Just "Not a valid integer value."
  where f = and . (map $ flip elem "0123456789") 

validate :: [Validator] -> Element -> String -> IO Element
validate vs e str = setInnerHtml e $
    case msum $ vs <*> [str] of
      Just msg -> msg
      Nothing  -> ""

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        win <- window
        input <- createElement "input"
        input ~> body
        div <- createElement "div"
        div ~> body
        let f = validate [nonEmpty, numeric] div in 
            attach $ fmap f (inputValue input)

    >>= onReady 

Scroll spy

Defines a menu whose items are automatically highlighted based on the scrollbar's vertical position.

You can also run this example directly in the browser.
module Main where

import Data.List                    ( find, sortBy )
import Data.Maybe                   ( maybeToList )
import Data.Function                ( on )
import Util.Base
import Util.DOM
import Util.FRP
import Util.HTML
import Util.HTML.Attributes
import Util.HTML.Elements

menuLink :: String -> String -> Html
menuLink title item = do
    a ! href ("#" ++ item) 
        $< title

buildElementList :: [String] -> IO [(Element, Element)]
buildElementList xs = (sequence $ map build xs) >>= return . sortedElements . concat 

build :: String -> IO [(Element, Element)]
build it = getElementById it >>= sequence . maybeToList . fmap f
  where f e = do li <- createElement "li"
                 innerHtml e >>= setInnerHtml li . renderHtml . flip menuLink it
                 return (e, li)

sortedElements :: [(Element, Element)] -> [(Element, Element)]
sortedElements = (sortBy . on compare) (negate . __offsetTop . fst)

findFirstElement :: [(Element, Element)] -> Int -> Maybe (Element, Element)
findFirstElement es y = flip find es $ \(e, _) -> __offsetTop e < y + 5

menuHtml :: [(Element, Element)] -> IO Element
menuHtml es = do 
    ul <- createElement "ul"
    setAttribute ul "id" "top-menu"
    sequence $ map (flip (~>) ul) $ reverse $ map snd es
    return ul

foreign import js "%1.offsetTop"
    __offsetTop :: Element -> Int

setClassAttribute :: Maybe (Element, Element) -> IO ()
setClassAttribute = sequence_ . maybeToList . fmap f 
  where f :: (Element, Element) -> IO ()
        f (_, e) = do old <- getElementsByClassName "active"
                      sequence $ flip map old $ \x -> setAttribute x "class" ""
                      setAttribute e "class" "active"

main :: IO ()
main = do
    wrap $ do
        body <- setup
        elements <- buildElementList ["item-1", "item-2", "item-3", "item-4", "item-5"]
        menuHtml elements >>= appendChild body
        let f = setClassAttribute . (findFirstElement elements)
        attach $ fmap f scrollYSignal

    >>= onReady

setup :: IO Element
setup = do
    body <- documentBody
    div  <- createElement "div"
    setInnerHtml div  $ "<h1 id=\"item-1\">Item 1</h1>" ++ blah
                     ++ "<h1 id=\"item-2\">Item 2</h1>" ++ blah
                     ++ "<h1 id=\"item-3\">Item 3</h1>" ++ blah
                     ++ "<h1 id=\"item-4\">Item 4</h1>" ++ blah
                     ++ "<h1 id=\"item-5\">Item 5</h1>" 
                     ++ (concat $ take 5 $ repeat blah)
    div ~> body
    st <- createElement "style"
    setInnerHtml st css
    st ~> body
    return body

blah :: String
blah = concat $ take 7 $ repeat "<p>Smorgosbord!</p>"

-- CSS borrowed from http://jsfiddle.net/mekwall/up4nu/
css :: String
css = "body { font-family: Helvetica, Arial; } \
      \#top-menu { list-style: none; position: fixed; z-index: 1; background: white; left: 250px; right: 0; top: 0; } \ 
      \#top-menu li { float: left; } \
      \#top-menu a { display: block; padding: 5px 25px 7px 25px; -webkit-transition: 1s all ease; -moz-transition: 1s all ease; transition: 1s all ease; border-top: 3px solid white; color: #666; text-decoration: none; } \
      \#top-menu a:hover { color: #000; } \
      \#top-menu li.active a { border-top: 3px solid #333; color: #333; font-weight: bold; }" 

Storage

import Util.Storage 
The Util.Storage module provides a key-value based database interface for persisting data in the browser's local storage. Assisted by the JSON module, any Haskell data type can be stored using a simple setter/getter paradigm.
data User = User
    { name     :: String
    , shoeSize :: Float
    } 

To store a record of this type in the database under the key "user-1", we use the syntax,

set "user-1" $ User "Franz" 42.5 

and to retrieve a value back from the store, we simply look inside the Maybe value returned by get:

u <- get "user-1"
case u of
    Nothing -> print "No such user in the DB."
    Just u  -> print $ (name u) ++ " has shoe size  " ++ show (shoeSize u) 

Note: Required instances for ToJSON and FromJSON omitted here for brevity.

This module is a wrapper for the jStorage JavaScript plugin. It can be downloaded from here. jStorage is © 2010‐2012 Andris Reinman, and licensed under Unlicense.

JS dependency

To use this module, you need to make sure that the jstorage.js plugin is available and included in your HTML file. The easiest way to achieve this is by adding a <script> tag dynamically to your document with the help of the addScript function from Util.Base.

main :: IO ()
main = do
    addScript "../../js/jstorage.js"
    wrap $ do
        ...
    >>= onLoad  -- We must use onLoad instead of onReady to wait for
                -- the added scripts to 

The essentials: Set and get

These are the two fundamental operations in jStorage.

-- | Saves a value to local storage.
set :: (ToJSON a) => String -> a -> IO ()
-- | Retrieves the stored value matching the given key, if one exists.
get :: (FromJSON a) => String -> IO (Maybe a) 

More efficient PackedString versions of these are also available under the names set' and get'.

More operations

Other useful functions are deleteKey, which is used to remove a key from the storage,

deleteKey :: String -> IO ()
deleteKey' :: PackedString -> IO () 

and index – it returns all keys currently in use as a list.

index :: IO [String]
index' :: IO [PackedString] 

Example: A product database

The following example saves a list of products in the database.

module Main where

import Util.Base
import Util.Storage

data Product = Product
    { pid      :: !Int
    , name     :: !String
    , category :: !Category
    , price    :: !Float
    } deriving (Show)

data Category = Clothing | Books | Food | Toys
    deriving (Show)

-- FromJSON and ToJSON boilerplate
instance FromJSON Product where
    fromJSON (Object v) = 
        Product <$> v .! "id"
                <*> v .! "name"
                <*> v .! "category"
                <*> v .! "price"
    fromJSON _ = Nothing

instance FromJSON Category where
    fromJSON (Str "clothing") = Just Clothing
    fromJSON (Str "books")    = Just Books
    fromJSON (Str "food")     = Just Food
    fromJSON (Str "toys")     = Just Toys
    fromJSON _ = Nothing

instance FromJSON [Product] where
    fromJSON = mapFrom

instance ToJSON Product where
    toJSON (Product { pid      = pid
                    , name     = name
                    , category = category
                    , price    = price }) 
      = obj [ "id"       |= pid
            , "name"     |= name
            , "category" |= category
            , "price"    |= price
            ]

instance ToJSON Category where
    toJSON Clothing = Str "clothing"
    toJSON Books    = Str "books"
    toJSON Food     = Str "food"
    toJSON Toys     = Str "toys"

instance ToJSON [Product] where
    toJSON = mapTo

-- Let's create a list of products
products :: [Product]
products =
    [ Product 1 "Clown shoes"        Clothing 10.0
    , Product 2 "Real World Haskell" Books    20.0
    , Product 3 "Palak paneer"       Food     15.0
    , Product 4 "Tuxedo"             Clothing 25.0
    ]

-- Store products in the local store
storeProducts :: IO ()
storeProducts = do
    set "my-products" products 

main :: IO ()
main = do
    addScript "../../js/jstorage.js"
    wrap $ do
        storeProducts
    >>= onLoad 

To read the data back from the database, change main to,

main = do
    addScript "../../js/jstorage.js"
    wrap $ do
        readProducts
        readNonExistingKey
        readExistingKeyIncorrectly
    >>= onLoad 

add these functions,

readProducts :: IO ()
readProducts = do
    p <- get "my-products" 
    case p of
      Nothing -> print "Key not found."
      Just ps -> f ps
    where f :: [Product] -> IO ()
          f s = print $ show s ++ "<br>"

readNonExistingKey :: IO ()
readNonExistingKey = do
    p <- get "most-likely-this-key-does-not-exist" 
    case p of
      Nothing         -> print $ "Nope!" ++ "<br>"
      Just (v :: Int) -> print "Who could have known?"

readExistingKeyIncorrectly :: IO ()
readExistingKeyIncorrectly = do
    p <- get "my-products" 
    case p of
      Nothing -> print $ "No parse, dude!" ++ "<br>"
      Just ps -> f ps
    where f :: [Int] -> IO ()
          f = print 

… then compile and run the program again. If everything works,

  • readProducts will print out the list of products;
  • readNonExistingKey will (most likely) print out “Nope!” since no key is found; and
  • readExistingKeyIncorrectly will also fail, since we cannot parse a Product as a [Int].

Router

import Util.Router
The Router module offers an easy-to-use solution for hashtag navigation. Util.Router makes it possible to pattern match over urls using simple do-notation.

As an example of what this looks like, the following route matches urls of the format post/show/%n where %n must be an integer value:

showPostRoute :: Route
showPostRoute = do
    atom "post"
    atom "show"
    pid <- num
    go $ showPost pid   -- some IO () action 

Instantiating the router

setMap installs the required event listeners and takes care of the rest of the internals.

setMap :: [Route] -> IO () 

This is how it is typically used:

main = do
    wrap $ do
        setMap [ showPostRoute
               , createPostRoute
               , editPostRoute
               ]
    >>= onLoad 

When a user changes the hash fragment of the url (or clicks on a link that does so), the application will invoke the router and run through the list of routes to look for a match.

Use go at the end of your route to run the desired action.

go :: IO () -> RouteM (IO ()) 

Behind the scenes, the RouteM type is a monadic parser combinator, and Route is a type synonym for RouteM (IO ()).

data RouteM a = RouteM { runR :: [String] -> (Maybe a, [String]) }
instance Monad RouteM where
    return a = RouteM $ \s -> (Just a, s)
    m >>= k  = RouteM $ \s -> let (a, t) = runR m s
                              in  case a of
                                    Nothing -> (Nothing, s)
                                    Just a' -> runR (k a') t
type Route = RouteM (IO ()) 

Building blocks

Even though it is perfectly possible to build your own, more complicated parsers (after all, that's the whole idea behind parser combinators), the following built-in types should cover the basic use cases:

  • atom Matches a string literal.
  • num Reads a numeric segment (an integer).
  • str Reads a text segment.

To match user/%s/comment/show/%n, we would write…

showCommentRoute :: Route
showCommentRoute = do
    atom "user"
    uname <- str
    atom "comment"
    atom "show"
    cid <- num
    go $ showComment uname cid

showComment :: String -> Int -> IO ()
showComment = ... 

That is pretty much all there is to it. Here is an example that prints out some links on the page to get a feel for how the navigation works.

You can also run this example directly in the browser.
module Main where
import Control.Applicative              ( (<$>), (<*>) )
import Data.Maybe                       ( maybeToList )
import Util.DOM
import Util.Router
import Util.String

showPost :: Int -> IO ()
showPost pid = out $ "show post #" ++ show pid ++ "<br>"

newPost :: IO ()
newPost = out $ "create new post" ++ "<br>"

byUser :: String -> IO ()
byUser n = out $ "show posts by user " ++ n ++ "<br>"

threeNumbers :: Int -> Int -> Int -> IO ()
threeNumbers a b c = out $
    show a ++ ", " ++ show b ++ ", " ++ show c ++ "<br>"

showPostRoute :: Route
showPostRoute = do
    atom "post"
    atom "show"
    pid <- num
    go $ showPost pid

newPostRoute :: Route
newPostRoute = do
    atom "post"
    atom "new"
    go newPost

postsByUserRoute :: Route
postsByUserRoute = do
    atom "post"
    atom "user"
    name <- str
    atom "please"
    go $ byUser name

anotherRoute :: Route
anotherRoute = do
    atom "read"
    a <- num
    b <- num
    c <- num
    go $ threeNumbers a b c

homeRoute :: Route
homeRoute =
    go $ out $ "Home" ++ "<br>"

main :: IO ()
main = do
    wrap $ do
        body <- documentBody
        div <- createElement "div"
        setInnerHtml div "<ul>\
                         \<li><a href=\"#post/show/1\">#post/show/1</a></li>\
                         \<li><a href=\"#post/new\">#post/new</a></li>\
                         \<li><a href=\"#post/user/bob/please\">#post/user/bob/please</a></li>\
                         \<li><a href=\"#read/1/2/3\">#read/1/2/3</a></li>\
                         \<li><a href=\"#/\">#/</a></li>\
                         \</ul>"
        div ~> body
        out <- createElement "div"
        setAttribute out "id" "myStdOut"
        out ~> body

        -- Assign routes
        setMap [ showPostRoute
               , newPostRoute
               , postsByUserRoute
               , anotherRoute
               , homeRoute 
               ]

    >>= onReady 

out :: String -> IO ()
out str = getElementById "myStdOut" >>= sequence_ . maybeToList . f 
  where f x = setInnerHtml <$> x <*> Just str

State

import Util.State
This module takes advantage of the DOM tree and a hidden property to store global application state.

The IsState typeclass provides default implementations for readState and setState:

class IsState a where
    readState :: IO a
    setState  :: a -> IO ()
    initState :: a
    readState = _readState ""
    setState  = _setState  ""

How to use

Create a data type with the state you want to make available to your application. Instantiate IsState and provide an implementation for initState.

data AppState = AppState
    { user    :: String
    , friends :: [String]
    }
    
instance IsState AppState where
    initState = AppState "" []

To allow multiple instances within a single application, you also need to reimplement readState and setState with a namespace identifier for each instance. Do not call _readState or _setState directly from application code. Instead, see below how these functions are used when instantiating IsState.

instance IsState AppState where
    initState = AppState "" []
    readState = _readState "app_state"
    setState  = _setState  "app_state"

Behind the scenes, the data is written to a hidden __state__ attribute on the window DOM object. The app_state suffix is added to to the name and thereby acts as a simple namespace.

This is how you typically use readState and setState in your application:

addFriend :: String -> IO ()
addFriend f = do
    (AppState u fs) <- readState
    setState $ AppState u (f:fs) 

Socket

import Util.Socket
The WebSocket protocol enables full-duplex communication over a single TCP connection.

Read more about the WebSocket specification here.

The connectAnd helper

The easiest way to implement a WebSocket client using Util.Socket is to take advantage of the connectAnd helper function:

connectAnd :: String              -- ^ URI
           -> (Socket -> IO ())   -- ^ Callback with socket handle as argument
           -> IO ()

For instance…

connectAnd "ws://localhost:9160" $ \sock -> do
    onOpen sock $ do
        send sock $ "Hi!"
    onMessage sock $ \msg -> do
        -- Process incoming message here...

Common errors

Compile-time errors

error: primStringToPackedString undeclared here (not in a function) 

This usually happens when you forget to specify the -tjs flag.

Runtime (JavaScript) errors

Uncaught ReferenceError: $ is not defined 

This happened to me when I used onReady instead of onLoad together with the addScript helper.

main = do
    addScript "../../js/jstorage.js"
    wrap $ do
        -- stuff
    >>= onLoad    -- Don't use onReady here 

It could also indicate that the file is missing, or not readable.

Uncaught TypeError: Object 0 has no method '__aN__' 

IRC: Join #liquidepsilon on FreeNode

© 2014 Johannes Hildén. Code licensed under BSD.