packages feed

ghcjs-vdom-0.2.0.0: src/GHCJS/VDOM.hs

{-# LANGUAGE QuasiQuotes #-}

{-|
   Bindings for the virtual-dom library.

   The virtual-dom diff function has been changed slightly to allow it to work
   with full functionality in asynchronous threads.

   It's possible to implement the bindings without the modifications at the
   cost of tail-call optimization and preemptive threading in the diff, by
   recursively forcing the thunks in synchronous threads.
 -}

module GHCJS.VDOM ( Attributes, Children
                  , Attributes', Children'
                  , VMount, VNode, VComp, DComp, Patch, DOMNode
                  , mount, unmount
                  , diff, patch
                  , memo, memoKey
                  ) where

import GHCJS.Types
import GHCJS.Foreign.QQ
import GHCJS.Prim
import GHCJS.Marshal.Pure

import Control.Monad
import Data.Coerce

import System.IO.Unsafe

import           GHCJS.VDOM.Internal.Types
import           GHCJS.VDOM.Internal.Thunk
import           GHCJS.VDOM.Internal       (j,J)
import qualified GHCJS.VDOM.Internal       as I

class MemoNode a where memoNode :: (J, [JSIdent], a) -> a

instance MemoNode VNode
  where
    memoNode (_,[],a) = a
    memoNode (k,xs,v) =
      let vd     = I.unsafeExportValue v
          xs1    = unsafePerformIO (toJSArray $ coerce xs)
      in VNode [j| h$vdom.th(`vd, `xs1, `k, true) |]
    {-# INLINE memoNode #-}

instance MemoNode b => MemoNode (a -> b)
  where
    memoNode (k,xs,f) = \a -> memoNode (k, I.objectIdent a:xs, f a)
    {-# INLINE memoNode #-}

memoKey :: MemoNode a => JSString -> a -> a
memoKey k = memo' (pToJSVal k)
{-# NOINLINE memoKey #-}

memo :: MemoNode a => a -> a
memo = memo' [j| $r = null; |]
{-# NOINLINE memo #-}

memo' :: MemoNode a => J -> a -> a
memo' k f = memoNode (k,[I.objectIdent f],f)
{-# INLINE memo' #-}

{-|
   Mount a virtual-dom tree in the real DOM. The mount point can be updated
   with patch.
-}
mount :: DOMNode -> VNode -> IO VMount
mount n v = do
  m <- VMount <$> [js| h$vdom.mount(`n) |]
  void $ patch m =<< diff m v
  return m
{-# INLINE mount #-}

{-|
   Remove a virtual-dom tree from the document. It's important to use
   unmount rather than removing the mount point any other way since this
   releases all associated Haskell data structures.
 -}
unmount :: VMount -> IO ()
unmount (VMount m) = [jsu_| h$vdom.unmount(`m); |]
{-# INLINE unmount #-}

{-|
   Compute a patch to update the mounted tree to match the virtual-dom tree
 -}
diff :: VMount -> VNode -> IO Patch
diff (VMount m) (VNode v) = do
  thunks <- [jsu| [] |]
  patch  <- [jsu| `m.diff(`v, `thunks) |]
  forceThunks thunks
  forcePatch [j| `patch.patch |]
  return (Patch patch)
{-# INLINE diff #-}

{-|
   Apply a patch to a mounted virtual-dom tree. Fails if the tree has already
   been patched after the diff was computed.
 -}
patch :: VMount -> Patch -> IO Bool
patch (VMount m) (Patch p) = [jsu| `m.patch(`p); |]
{-# INLINE patch #-}