{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.FFI -- Copyright : (C) 2016-2018 David M. Johnson -- License : BSD3-style (see the file LICENSE) -- Maintainer : David M. Johnson -- Stability : experimental -- Portability : non-portable ---------------------------------------------------------------------------- module Miso.FFI ( JSM , forkJSM , asyncCallback , asyncCallback1 , callbackToJSVal , objectToJSVal , ghcjsPure , syncPoint , addEventListener , windowAddEventListener , windowInnerHeight , windowInnerWidth , eventPreventDefault , eventStopPropagation , now , consoleLog , consoleLogJSVal , stringify , parse , clearBody , objectToJSON , set , getBody , getDoc , getElementById , diff' , integralToJSString , realFloatToJSString , jsStringToDouble , delegateEvent , copyDOMIntoVTree , swapCallbacks , releaseCallbacks , registerCallback , focus , blur , scrollIntoView , alert ) where import Control.Concurrent import Control.Monad.IO.Class import Data.Aeson hiding (Object) import Data.JSString import GHCJS.Marshal import GHCJS.Types import qualified JavaScript.Object.Internal as OI import Language.Javascript.JSaddle hiding (Success, obj, val) -- | Run given `JSM` action asynchronously, in a separate thread. forkJSM :: JSM () -> JSM () forkJSM a = do ctx <- askJSM _ <- liftIO (forkIO (runJSM a ctx)) pure () asyncCallback :: JSM () -> JSM Function asyncCallback a = asyncFunction (\_ _ _ -> a) asyncCallback1 :: (JSVal -> JSM ()) -> JSM Function asyncCallback1 f = asyncFunction (\_ _ [x] -> f x) callbackToJSVal :: Function -> JSM JSVal callbackToJSVal = toJSVal objectToJSVal :: Object -> JSM JSVal objectToJSVal = toJSVal -- | Set property on object set :: ToJSVal v => JSString -> v -> OI.Object -> JSM () set (unpack -> "class") v obj = do classSet <- ((pack "class") `elem`) <$> listProps obj if classSet then do classStr <- fromJSValUnchecked =<< getProp (pack "class") obj vStr <- fromJSValUnchecked =<< toJSVal v v' <- toJSVal (classStr <> pack " " <> vStr) setProp (pack "class") v' obj else do v' <- toJSVal v setProp (pack "class") v' obj set k v obj = do v' <- toJSVal v setProp k v' obj -- | Register an event listener on given target. addEventListener :: JSVal -- ^ Event target on which we want to register event listener -> JSString -- ^ Type of event to listen to (e.g. "click") -> (JSVal -> JSM ()) -- ^ Callback which will be called when the event occurs, the event will be passed to it as a parameter. -> JSM () addEventListener self name cb = do _ <- self # "addEventListener" $ (name, asyncFunction (\_ _ [a] -> cb a)) pure () -- | Registers an event listener on window windowAddEventListener :: JSString -- ^ Type of event to listen to (e.g. "click") -> (JSVal -> JSM ()) -- ^ Callback which will be called when the event occurs, the event will be passed to it as a parameter. -> JSM () windowAddEventListener name cb = do win <- jsg "window" addEventListener win name cb eventStopPropagation :: JSVal -> JSM () eventStopPropagation e = do _ <- e # "stopPropagation" $ () pure () eventPreventDefault :: JSVal -> JSM () eventPreventDefault e = do _ <- e # "preventDefault" $ () pure () -- | Retrieves the height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar. -- -- See windowInnerHeight :: JSM Int windowInnerHeight = fromJSValUnchecked =<< jsg "window" ! "innerHeight" -- | Retrieves the width (in pixels) of the browser window viewport including, if rendered, the vertical scrollbar. -- -- See windowInnerWidth :: JSM Int windowInnerWidth = fromJSValUnchecked =<< jsg "window" ! "innerWidth" -- | Retrieve high resolution time stamp -- -- See now :: JSM Double now = fromJSValUnchecked =<< (jsg "performance" # "now" $ ()) -- | Outputs a message to the web console -- -- See consoleLog :: JSString -> JSM () consoleLog v = do _ <- jsg "console" # "log" $ [toJSString v] pure () -- | Console-logging consoleLogJSVal :: JSVal -> JSM () consoleLogJSVal v = do _ <- jsg "console" # "log" $ [v] pure () -- | Converts a JS object into a JSON string stringify :: ToJSON json => json -> JSM JSString {-# INLINE stringify #-} stringify j = do v <- toJSVal (toJSON j) fromJSValUnchecked =<< (jsg "JSON" # "stringify" $ [v]) -- | Parses a JSString parse :: FromJSON json => JSVal -> JSM json {-# INLINE parse #-} parse jval = do val <- fromJSValUnchecked =<< (jsg "JSON" # "parse" $ [jval]) case fromJSON val of Success x -> pure x Error y -> error y -- | Clear the document body. This is particularly useful to avoid -- creating multiple copies of your app when running in GHCJSi. clearBody :: JSM () clearBody = (jsg "document" ! "body" <# "innerHtml") [""] objectToJSON :: JSVal -- ^ decodeAt :: [JSString] -> JSVal -- ^ object with impure references to the DOM -> JSM JSVal objectToJSON = jsg2 "objectToJSON" -- | Retrieves a reference to document body. -- -- See getBody :: JSM JSVal getBody = jsg "document" ! "body" -- | Retrieves a reference to the document. -- -- See getDoc :: JSM JSVal getDoc = jsg "document" -- | Returns an Element object representing the element whose id property matches the specified string. -- -- See getElementById :: JSString -> JSM JSVal getElementById e = getDoc # "getElementById" $ [e] diff' :: OI.Object -- ^ current object -> OI.Object -- ^ new object -> JSVal -- ^ parent node -> JSVal -- ^ document -> JSM () diff' a b c d = () <$ jsg4 "diff" a b c d integralToJSString :: Integral a => a -> JSString integralToJSString = pack . show . toInteger realFloatToJSString :: RealFloat a => a -> JSString realFloatToJSString x = (pack . show) (realToFrac x :: Double) jsStringToDouble :: JSString -> Double jsStringToDouble = read . unpack delegateEvent :: JSVal -> JSVal -> JSM JSVal -> JSM () delegateEvent mountPoint events getVTree = do cb' <- asyncFunction $ \_ _ [continuation] -> do res <- getVTree _ <- call continuation global res pure () delegateEvent' mountPoint events cb' delegateEvent' :: JSVal -> JSVal -> Function -> JSM () delegateEvent' mountPoint events cb = () <$ jsg3 "delegate" mountPoint events cb -- | Copies DOM pointers into virtual dom -- entry point into isomorphic javascript copyDOMIntoVTree :: Bool -> JSVal -> JSVal -> JSM () copyDOMIntoVTree logLevel mountPoint a = () <$ jsg3 "copyDOMIntoVTree" logLevel mountPoint a -- TODO For now, we do not free callbacks when compiling with JSaddle -- | Pins down the current callbacks for clearing later swapCallbacks :: JSM () swapCallbacks = pure () -- | Releases callbacks registered by the virtual DOM. releaseCallbacks :: JSM () releaseCallbacks = pure () registerCallback :: JSVal -> JSM () registerCallback _ = pure () -- | Fails silently if the element is not found. -- -- Analogous to @document.getElementById(id).focus()@. focus :: JSString -> JSM () focus a = () <$ jsg1 "callFocus" a -- | Fails silently if the element is not found. -- -- Analogous to @document.getElementById(id).blur()@ blur :: JSString -> JSM () blur a = () <$ jsg1 "callBlur" a -- | Calls @document.getElementById(id).scrollIntoView()@ scrollIntoView :: JSString -> JSM () scrollIntoView elId = do el <- jsg "document" # "getElementById" $ [elId] _ <- el # "scollIntoView" $ () pure () -- | Calls the @alert()@ function. alert :: JSString -> JSM () alert a = () <$ jsg1 "alert" a