Liquid Epsilon is a smorgasbord of modules for building single-page, client-side web and mobile applications, using Haskell and the UHC compiler.
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.
For instructions on how to install UHC-JS, see this page.
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)
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.
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.
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.
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!)
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.
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
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.
import Util.DOM
Think of this module as a thin wrapper for the more common JavaScript DOM access functions, such asdocument.getElementById()anddocument.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)
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
import Util.String
Util.Stringexports thepackandunpackfunctions, for conversion between HaskellStrings 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.
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.
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
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 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..."
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
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.
toStr and fromStrtoStr 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]"
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
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
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"}
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.
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 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
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> \
\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
import Util.HTML.Handlebars
This module extends the functionality ofUtil.Handlebarsto include support for usingUtil.HTMLcombinators 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
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.
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.
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
_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
_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"
_bindSignalOnUsing _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
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
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
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; }"
import Util.Storage
TheUtil.Storagemodule provides a key-value based database interface for persisting data in the browser's local storage. Assisted by theJSONmodule, 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.
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
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'.
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]
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; andreadExistingKeyIncorrectly will also fail,
since we cannot parse a Product as a
[Int].import Util.Router
The Router module offers an easy-to-use solution for hashtag navigation.Util.Routermakes it possible to pattern match over urls using simpledo-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
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 ())
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
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 ""
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)
import Util.Socket
The WebSocket protocol enables full-duplex communication over a single TCP connection.
Read more about the WebSocket specification here.
connectAnd helperThe 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...
error: primStringToPackedString undeclared here (not in a function)
This usually happens when you forget to specify the
-tjs flag.
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__'
© 2014 Johannes Hildén. Code licensed under BSD.