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.String
exports thepack
andunpack
functions, for conversion between HaskellString
s 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 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]"
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
Book
s.
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.Handlebars
to include support for usingUtil.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
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"
_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
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.Storage
module provides a key-value based database interface for persisting data in the browser's local storage. Assisted by theJSON
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.
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.Router
makes 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.