Haskell in web browser/Haskell web toolkit

From HaskellWiki
< Haskell in web browser
Revision as of 15:18, 23 March 2008 by DimitryGolubovsky (talk | contribs) (display-only elements)

Jump to: navigation, search





type Widget =  THTMLDocument -> THTMLElement -> Bool


HTML elements as widgets


type ECRF n = (THTMLDocument -> CPS Bool n)




Passive widgets



passive :: CNode n => ECRF n -> Widget






Composition combinators





mkTextFl = flip mkText
container mkDiv << passive (mkTextFl "Hello World")

To simplity the code, HsWTK defines these two functions:

  • textP which is equivalent to passive (flip mkText txt)
  • |<< which is defined as c |<< d = container c << d, that is the word container may be omitted.

Document body

The toplevel widget (one that is not nested in any other widget) has to be inserted into the HTML document body. This is acieved by applying the function docBodyC to the toplevel widget. This application often becomes the main function of a Web application. Please note that document body is not a Widget.

Code example - Hello Web


-- Begin Pasteable Code --
module TutEx1 where

import DOM.Level2.HTMLSpanElement
import Graphics.UI.HsWTK

-- The Main Function

main = docBodyC mainW

-- The Toplevel Widget

mainW = (mkSpan |<< (textP "Hello " +++ textP "Web"))
-- End   Pasteable Code --


The example shows all the facilities of HsWTK we recently discussed:

  • creation of a HTML Element based widget (mkSpan)
  • inserting the toplevel widget into the document body (docBodyC)


Decorators are not Widgets: they are tools to change properties of a widget they are applied to, at the moment of that widget creation.

Earlier we discussed setters defined for HTML Elements' writable attributes. HsWTK defines a function decorate which when applied to a setter, produces a decorator. An example of such a decorator is withTitle defined as shown below:

withTitle :: (CHTMLElement b) => ((a -> ((b -> (CPS c b)) -> d)) -> (String -> (a -> d)))
withTitle = decorate set'title

The set'title function is a setter for the title attribute of HTMLElement.

Decorators are binary functions taking the Widget to decorate as the first argument, and the value used to decorate as the second. In our case, this looks like this:

... mkDiv `withTitle` "This div has a title" |<< textP "titled div"

HsWTK defines some frequently used decorators, such as withClass to specify the class attribute of an element used in a widget, or withSrc to use with an < IMG > element to specify image source.

Inline style

A special decorator, withStyle, represents a more complicated case. It does not alter attributes/properties of a widget element it is used with; instead it affects inline style properties of the widget element.

While arttribute-based decorators take simple values (strings, less often numbers) to decorate widget elements, withStyle takes a list of name-value pairs. Both names and values are strings, and should conform to the Cascading Style Sheets Specification.

Property names and values are paired using the := data constructor. The latter is just a binary infix data constructor, and it is used just to improve the code appearance, so CSS property assignments look more natural.

Our next pasteable examples deal with widget elements' inline style manipulations. The following example displays a centered white bold text on green background spanning over 45% of browser window width floating to the right side of browser window.

-- Begin Pasteable Code --
module TutEx2 where

import DOM.Level2.HTMLDivElement
import Graphics.UI.HsWTK

main = docBodyC mainW

mainW = mkDiv `withStyle` ["display" := "inline"
                          ,"float" := "right"
                          ,"width" := "45%"
                          ,"text-align" := "center"
                          ,"background-color" := "green"
                          ,"color" := "white"
                          ,"font-weight" := "bold"] |<< textP "Hello Web"
-- End   Pasteable Code --

Our next example is more contrived. It displays a line of text with each character colored differently, and colors are taken from an infinitely repeating sequence. Note the use of nowidget: it is used with foldr as a "starting value".

-- Begin Pasteable Code --
module TutEx2a where

import DOM.Level2.HTMLSpanElement
import Graphics.UI.HsWTK

-- Main function

main = docBodyC mainW

-- Text to display

txt = "Changing Colors of World Wide Web"

-- Make an infinite list of colors for each character to display

fgcs = cycle ["red", "blue", "green", "cyan", "magenta"]

-- Sequence as many spans as needed for colored characters

mainW = foldr (+++) nowidget (zipWith spanc fgcs txt)

-- Color a single character by creating a span properly styled

spanc f c = mkSpan `withStyle` ["color" := f] |<< textP [c]
-- End   Pasteable Code --


At this point, we are able to create static layout of Widgets. No way has been shown yet how to make GUI elements exchange information among themselves.

Activators serve exactly this purpose. Activators are functions, usually endlessly looped which are executed each in its own thread. These functions repeatedly receive messages from Message Boxes, and also intercept events induced by user. Activators also are able to modify on-the-fly properties of Widgets to which they are "attached". Some Activators also perform data exchange over XMLHTTP.

Method of inter-widget communication described here is derived from one discussed in the following paper:


HsWTK defines a type for these functions:

type ACTF = THTMLElement -> Bool

This type has one thing in common with the Widget type: a THTMLElement is one of function's arguments.

HsWTK also defines a function active which being applied to an Activator turns it into a Widget, that is, an Activator may be nested within a container: that's what was meant above for an Activator "attached" to a Widget.

Our next example consists of two <INPUT> elements: when something is typed in the left one, it is repeated in the right one:

module TutEx3 where

import Graphics.UI.HsWTK
import Control.Concurrent.JSThreads

main = docBodyC mainW

mainW = msgBox $ \ibx2 ->
        msgBox $ \dummy ->
        inputI |<< active (evtBCastA "keyup" rtgt [ibx2])
    +++ inputI |<< active (fwdValueA ibx2 dummy)

rtgt e = readTargetU e FwdUpdSet

Let's go line by line. As new things are introduced, they will be discussed in appropriate sections.

Message boxes and activators

Message boxes were discussed earlier as means for inter-thread communications. Each Activator is represented by a separate thread. Unlike previous code examples, the main Widget code contains two calls to the msgBox function. The msgBox function passes reference to newly created Message box to its continuation. Thus, after the following code:

msgBox $ \ibx2 ->
msgBox $ \dummy ->

is executed, two Message boxes are created.

Wiring the static layout

The following code of the main Widget would have produced the same static layout of two input elements:

mainW = inputI |<< nowidget
    +++ inputI |<< nowidget

Note the use of nowidget here: the inputI function produces an <INPUT> element, and the |<< combinator assumes that the <INPUT> element is a container, that is, should have something nested.

This approach may be used during Web GUI design: make Widgets containers where necessary, but nest nowidget's instead of Activators.

Compare the code of mainW above with the code from our example:

mainW = ...

Each <INPUT> element got an Activator. As it may be guessable, Activator attached to the first declared (showing at the left of Web page) <INPUT> element, transmits its value (whatever is typed in), and Activator attached to the <INPUT> element declared second (showing right of the first <INPUT>) receives the value.

Mapping and broadcasting events

HsWTK defines the following Activator function: evtBCastA:

evtBCastA :: CEvent e => String -> (e -> CPS Bool v) -> [MSGBOX Bool v] -> ACTF

The first argument of String type is the type of event this Activator responds to. Only one event type may be specified. Event types are derived from on- attribute names, e. g. "keyup" corresponds to onkeyup event, etc.

The second argument is a CPS-style function mapping an event to some value of type v. Such a function may retrieve certain parameters of an event (such as codeof key pressed), or event produce some side effects, although this is not recommended.

The third argument is list of Message boxes where the mapped value will be broadcast over. Broadcast will be performed using the sendMsg_ function, so there is possibility of message non-delivery.

HsWTK defines two functions to use as the second argument of evtBCastA:

  • evt2ConstU: maps any event to the given constant value
  • readTargetU: maps any event to the value (always of type String) retrieved from its target element. The target element must support the value property.

The latter function is used in our example. Basically the Activator attached to the left <INPUT> element intercepts each onkeyup event, retrieves event target value (that is, the <INPUT> element it is attached to), maps it to the value forwarding message (see below), and sends to the Message box that other <INPUT> element may be listening to by means of its Activator.

Value forwarding protocol

Value forwarding protocol is a convention set by HsWTK regarding the way how Widgets values may be set externally, or queried. A conforming Widget should have an Activator parameterized with two Message boxes. The first Message Box is for receiving messages of type FwdValueMsg. The second Message Box is for sending messages of type String.

The Activator should act upon the receipt of FwdValueMsg as follows:

  • FwdCurr: forward the current value, not changing it
  • FwdCurrSet s: forward the current value to the default Message Box, update with new value s
  • FwdUpdSet s: update element's value with new value s, forward the new value to the default Message Box
  • FwdCurrTo m: like FwdCurr but to a specified Message Box m
  • FwdCurrSetTo m s: like FwdCurrSet but to a specified Message Box m
  • FwdUpdSetTo m s: like FwdUpdSet but to a specified Message Box m

Messages FwdCurrTo, FwdCurSetTo, FwdUpdSetTo cause the value of the Widget to be forwarded to a Message Box different from one the Activator is parameterized with.

In our code example, the second <INPUT> element has an Activator fwdValueA:

... inputI |<< active (fwdValueA ibx2 dummy)

This is a "standard" Activator provided by HsWTK for Widgets based on HTML elements with attribute value defined. The first Message Box is the same where the first <INPUT> element's Activator broadcasts the value upon each key release.

The second Message Box is not used in any way, but it is there since fwdValueA requires it.

Now, looking at the event mapping function of the first <INPUT> element:

rtgt e = readTargetU e FwdUpdSet

which means that the constructor FwdUpdSet is applied to the value retrieved from the event target element (that is, the first <INPUT>), yielding the message per Value forwarding protocol. This value will be received by the second <INPUT> element's Activator, and will subsequently show in the element.

More complex widgets

Our next example shows a Widget which has some functionality encapsulated, while externally this is just an <INPUT> element. The example features an input field which validates its value (as it is typed in) against a regular expression, and changes its border color to green when validation passes, and to red otherwise.

-- Begin Pasteable Code --
module TutEx4 where

import Data.JRegex
import CDOM.Level2.DomUtils
import Graphics.UI.HsWTK
import DOM.Level2.HTMLSpanElement
import Control.Concurrent.JSThreads

main = docBodyC mainW

mainW = msgBox $ \valmb ->
        textP "Version (X.Y.Z) etc.:"
    +++ valInput (JRegex "^[0-9]+(\\.[0-9]+)*$") ("green", "red") valmb

valInput rgx coltp mb = 
  msgBox $ \dummy ->
  inputI |<< (active (evtBCastA "keyup" (valrx rgx coltp) [])
          +++ active (fwdValueA mb dummy))

valrx rgx (ct, cf) e k = readTargetU e (jrxTest rgx "") $ \bv ->
  targetElement e 
    (set'style ["border-color" := if bv then ct else cf]) $ \_ -> k True
-- End   Pasteable Code --

Regular expression in this example matches numbers separated by dots (like 1.2.3). Readers are encouraged to try other regular expressions as well.

Understanding of how this code works is left as reader's excercise. Hint: evtBCastA is called with empty list of Message Boxes. All event processing work is done on the single HTML element (event target).

Display-only widgets

Below is an example of code similar to the example with two <INPUT> elements except that user's input is displayed in a non-editable (display-only) Widget.

-- Begin Pasteable Code --
module TutEx5 where

import Graphics.UI.HsWTK
import DOM.Level2.HTMLDivElement
import DOM.Level2.HTMLSpanElement
import Control.Concurrent.JSThreads

main = docBodyC mainW

fleft = mkDiv `withStyle` ["float" := "left"]
fclear = mkDiv `withStyle` ["clear" := "both"] |<< nowidget

mainW = msgBox $ \ibx2 ->
        mkDiv |<< (fleft |<< inputI |<< active (evtBCastA "keyup" rtgt [ibx2])
               +++ fleft |<< mkDiv `withStyle` 
                                     ["border" := "1px solid gray"
                                     ,"width" := "100px"
                                     ,"padding" := "3px"
                                     ,"text-align" := "right"
                                     ,"overflow" := "hidden"] 
                               |<< (textP "\160" 
                                +++ mkSpan |<< active (updMapA "" unFwd ibx2))
               +++ fclear)

rtgt e = readTargetU e FwdUpdSet

unFwd (FwdUpdSet s) = s
unFwd _ = ""
-- End   Pasteable Code --

In this example, both <INPUT> and <DIV> elements are enclosed within left-floating <DIV>'s for proper placement. The character with code 160 (that is, &nbsp) is inserted within a framed <DIV> to make sure the frame has full length when nothing is displayed. Finally, a <SPAN> element is nested within the framed <DIV>. The special Activator, updMapA is attached to the latter. This activator is parameterized with one Message Box to receive messages of any type. Another parameter of updMapA is a function that maps input messages to Strings. In this example, the message sent by the <INPUT> element to be displayed, is expected to be wrapped into the FwdUpdSet constructor. Please note that this is not a full emulation of Value forwarding protocol as nothing would be sent in response to messages like FwdCurrTo. Please note also that when building a display-only element, avoid nesting anything other than the appopriate Activator within the element that performs actual display. The updMapA Activator is based on the updateU function which replaces the first child of given node with given String value. Other children remain unchanged. With this logic, some interesting evvects may be achieved, but this feature should be used with caution.