packages feed

haste-lib-0.6.0.0: src/Haste/Foreign.hs

{-# LANGUAGE CPP, OverloadedStrings #-}
-- | High level JavaScript foreign interface.
module Haste.Foreign
  ( -- * Conversion to/from JSAny
    ArrView, ToAny (..), FromAny (..), JSAny
  , Opaque, toOpaque, fromOpaque
  , nullValue, toObject, has, get, index, isUndefined
  , getMaybe, hasAll, lookupAny, JSException (..)

    -- * Importing and exporting JavaScript functions
  , FFI, JSFunc
  , ffi, constant, export
  , safe_ffi, StaticPtr
  , withLibraries
  ) where
import Haste.Prim.Foreign
import Haste.Prim (JSString)
import qualified Haste.JSString as J
import Control.Monad (foldM)

-- For withLibraries
import Haste.Concurrent
import Haste.DOM.JSString
import Haste.Events

-- For array instances
import Haste.Foreign.Array (ArrView)

-- | Read a member from a JS object. Succeeds if the member exists.
getMaybe :: FromAny a => JSAny -> JSString -> IO (Maybe a)
getMaybe a k = do exists <- has a k
                  if exists then Just <$> get a k
                    else pure Nothing

-- | Checks if a JS object has a list of members. Succeeds if the JS object
--   has every member given in the list. 
hasAll :: JSAny -> [JSString] -> IO Bool
hasAll a ks = and <$> mapM (has a) ks


-- | Looks up an object by a `.`-separated string. Succeeds if the lowest
--   member exists.
--
-- Usage example:
-- 
-- >>> {'child': {'childrer': {'childerest': "I am very much a child."}}}
--
-- Given the JS Object above, we can access the deeply nested object,
--  childerest, by lookupAny as in the below example
--
-- >>> lookupAny jsObject "child.childrer.childerest"
lookupAny :: JSAny -> JSString -> IO (Maybe JSAny)
lookupAny root i = foldM hasGet (Just root) $ J.match dotsplit i
  where hasGet Nothing       _     = pure Nothing
        hasGet (Just parent) ident = do h <- has parent ident
                                        if h then Just <$> get parent ident
                                          else pure Nothing
        dotsplit = J.regex "[^.]+" "g"

-- | Wait for the given libraries to load before executing the given
--   computation. Note that this function returns immediately, BEFORE the given
--   libraries are loaded.
withLibraries :: [JSString] -> IO () -> IO ()
withLibraries libs m = concurrent $ do
    vars <- mapM (const newEmptyMVar) libs
    liftIO . sequence_ $ zipWith addLib libs vars
    mapM_ takeMVar vars
    liftIO m
  where
    addLib lib var = do
      s <- newElem "SCRIPT" `with` ["src" =: lib]
      s `onEvent` Load $ \_ -> concurrent $ putMVar var ()
      appendChild documentBody s