Question

Prelude. I'm new to Haskell and functional programming at all, but I can't help coding in Haskell, because it' so exiciting! Thus, my question could be very simple and/or trivial. I'm still reading "Learn You a Haskell for Great Good", and Monads are still in the way.

Consider following behaviour:

  • Create a stack of events (simply array)
    • Listen for mousemove events, when event fired, push them into stack
  • Run render loop:
    • use requestAnimationFrame to start loop
    • every time check if there are any events stacked
    • get last event from stack, make some stuff with it and render
    • empty stack
    • request next frame

What I can't realize is how to get interested value from event, e.g. pageX or clientX, it's looks like I have to do something with data definitions, and I'm stuck with it. Please help.

P.S. I also can't understand how to check result of foreign function, e.g. when I try to get DOM element the result may be element or null, thus, say, getElById is impure, and I need to check what I've got in result. How to realize it? Seems I can use something like: data Element = Null | Element ... and then pattern match the result, but how to make foreign function to be aware of expected result?

UPDATE
Here working example of desired logics:

module MouseTracker where

import FFI
import Prelude

data Element
data Event
type EventStack = [Event]
data Frame


consLog :: a -> Fay ()
consLog = ffi "console.log(%1)"


reqFrame :: (Frame -> Fay ()) -> Fay ()
reqFrame = ffi "requestAnimationFrame(%1)"


getDoc :: Fay Element
getDoc = ffi "document"


createEl :: String -> Fay Element
createEl = ffi "document.createElement(%1)"


elById :: String -> Fay Element
elById = ffi "document.getElementById(%1)"


elSetId :: Element -> String -> Fay ()
elSetId = ffi "%1.setAttribute('id',%2)"


elSetHtml :: Element -> String -> Fay ()
elSetHtml = ffi "%1.innerHTML = %2"


docBodyAppend :: Element -> Fay ()
docBodyAppend = ffi "document.body.appendChild(%1)"


elListenWith :: Element -> String -> (Event -> Fay ()) -> Fay ()
elListenWith = ffi "%1.addEventListener(%2, %3)"


winListenWith :: String -> (Event -> Fay ()) -> Fay ()
winListenWith = ffi "window.addEventListener(%1,%2)"


docListener :: String -> (Event -> Fay ()) -> Fay ()
docListener evtType fn = do
    doc <- getDoc
    elListenWith doc evtType fn




createTrace :: Fay ()
createTrace = do
    div <- createEl "div"
    docBodyAppend div
    elSetId div "trace"


createStack :: Fay ()
createStack = ffi "window._eventStack = []"


getStack :: Fay EventStack
getStack = ffi "window._eventStack"


updateStack :: EventStack -> Fay ()
updateStack = ffi "window._eventStack = %1"


emptyStack :: Fay ()
emptyStack = createStack




listenMice :: Fay()
listenMice = docListener "mousemove" trackMouse
    where trackMouse :: Event -> Fay ()
          trackMouse evt = do
              stk <- getStack
              updateStack (evt:stk)


renderFrame :: Frame -> Fay ()
renderFrame frame = do
     stack <- getStack
     if length stack > 0
           then (do
                   let e = head stack
                   traceEventCoord e)
           else (do return ())
     reqFrame renderFrame
     emptyStack
     where traceEventCoord :: Event -> Fay ()
           traceEventCoord e = do
               eX <- pageX e
               eY <- pageY e
               let str = show eX ++ " x " ++ show eY
               trace <- elById "trace"
               elSetHtml trace str


pageX :: Event -> Fay Int
pageX = ffi "%1['pageX']"
pageY :: Event -> Fay Int
pageY = ffi "%1['pageY']"


main = do
    docListener "DOMContentLoaded" $ \ _ -> do
        putStrLn "DOM ready"
        createTrace
        createStack
        listenMice
        reqFrame renderFrame
Was it helpful?

Solution

See this answer for the different ways to declare data types and their tradeoffs. Are you creating the events yourself or are you wrapping DOM events? If you are wrapping I'd suggest to use an EmptyDataDecl. That would mean that you don't have constructors for each kind of event but you can add functions that check that type for you. If you create your own events it makes more sense to use a normal data declaration with constructors.

When wrapping DOM events you'd then end up with:

data Event
pageX :: Event -> Fay Int
pageX = ffi "%1['pageX']"

Fay has the Nullable type to deal with nullable values:

getElementById :: String -> Nullable Element
getElementById = ffi "document['getElementById'](%1)"

This is described in more detail on the wiki

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top