haste-compiler-0.2.99: libraries/haste-lib/src/Haste/Reactive/DOM.hs
{-# LANGUAGE GADTs, FlexibleInstances, MultiParamTypeClasses,
FlexibleContexts, OverlappingInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | DOM events and utilities for the Haste reactive library.
module Haste.Reactive.DOM (clicked,valueOf,valueAt,ElemProp,elemProp) where
import FRP.Fursuit
import Haste
import qualified Data.Map as M
import System.IO.Unsafe (unsafePerformIO)
import Data.IORef
{-# NOINLINE eventHandlers #-}
-- | Contains a list of all installed event handlers.
eventHandlers :: JSType a => IORef (M.Map (ElemID, Event IO e) (Signal a))
eventHandlers = unsafePerformIO $ newIORef M.empty
-- | Represents a property of a DOM object.
data ElemProp where
D :: ElemID -> PropID -> ElemProp
-- | Create a 'DOMObject' from a string describing the object. For example,
-- domObj "myobject.value" corresponds to the value attribute of the object
-- with the ID "myobject".
elemProp :: String -> ElemProp
elemProp str =
case span (/= '.') str of
([], _) -> error "elemProp: No object ID given!"
(_, []) -> error "elemProp: No object attribute given!"
(obj, attr) -> D obj (tail attr)
unlessExists :: JSType a => ElemID -> Event IO e -> IO (Signal a) -> Signal a
unlessExists eid evt create = new $ do
handlers <- readIORef eventHandlers
case M.lookup (eid, evt) handlers of
Just s -> return s
_ -> do
sig <- create
writeIORef eventHandlers (M.insert (eid, evt) sig handlers)
return sig
-- | An event that gets raised whenever the element with the specified ID is
-- clicked.
clicked :: ElemID -> Signal ()
clicked eid = unlessExists eid OnClick clickedIO
where
clickedIO = withElem eid $ \e -> do
(p,s) <- pipe ()
_ <- setCallback e OnClick (\_ _ -> write p ())
return s
-- | The value property of the given element, updated whenever an onchange
-- event is raised.
valueOf :: JSType a => ElemID -> Signal a
valueOf e = e `valueAt` OnChange
-- | The value property of the given element, triggered on a custom event.
valueAt :: (JSType a, Callback e) => ElemID -> Event IO e -> Signal a
valueAt eid evt = filterMapS fromString $ unlessExists eid evt valueAtIO
where
valueAtIO = withElem eid $ \e -> do
str <- getProp e "value"
(src, sig) <- pipe str
success <- setCallback e evt $ constCallback $ do
getProp e "value" >>= write src
if (not success)
then error $ "Browser doesn't support sane event handlers!"
else return sig
-- | Like show, but strips enclosing quotes.
toStr :: Show a => a -> String
toStr x =
case show x of
('"':xs) -> init xs
xs -> xs
instance Show a => Sink ElemProp a where
(D obj attr) << val = withElem obj $ \e -> sink (setProp e attr . toStr) val
-- | Replace the sink element's list of child nodes whenever a new list of
-- nodes comes down the pipe.
instance Sink Elem [Elem] where
e << val = sink (setChildren e) val
-- | Same as the instance for [Elem].
instance Sink Elem [IO Elem] where
e << val = sink (\children -> sequence children >>= setChildren e) val
-- | Set the sink element's innerHTML property whenever a new string comes down
-- the pipe.
instance Sink Elem String where
e << val = sink (setProp e "innerHTML") val