packages feed

react-flux-1.0.1: example/purecss-side-menu/NavStore.hs

{-# LANGUAGE TypeFamilies, DeriveGeneric, DeriveAnyClass, OverloadedStrings #-}
-- | A store to hold the current page being viewed.
module NavStore where

import Control.DeepSeq (NFData)
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import React.Flux

import GHCJS.Types (JSRef, JSString)
import GHCJS.Foreign.Callback (Callback, syncCallback1, OnBlocked(..))
import GHCJS.Marshal (fromJSRef)
import qualified Data.JSString as JSString

data NavPageId = Page1
               | Page2
               | Page3
    deriving (Show, Eq, Enum, Bounded, Typeable, Generic, NFData)

-- | The page title (used for `document.title`) and the url (used for the history API and displayed
-- in the browser location bar).
pageTitleAndUrl :: NavPageId -> (String, String)
pageTitleAndUrl Page1 = ("Page 1 Title", "/page1")
pageTitleAndUrl Page2 = ("Page 2222", "/pages/page2")
pageTitleAndUrl Page3 = ("Page Three Title", "/page3.html")

allPageIds :: [NavPageId]
allPageIds = [(minBound :: NavPageId) .. ]

data NavAction = ChangePageTo NavPageId -- ^ use to change the page
               | BackToPage NavPageId -- ^ used from the history handler to go back
               | ToggleSideMenu
    deriving (Show, Eq, Typeable, Generic, NFData)

data NavState = NavState
  { sideMenuOpen :: Bool
  , currentPageId :: NavPageId
  } deriving (Show, Eq, Generic, Typeable)

instance StoreData NavState where
    type StoreAction NavState = NavAction
    transform (ChangePageTo p) _ = do
        historyPushState p
        setDocTitle p
        return NavState { currentPageId = p, sideMenuOpen = False }
    transform (BackToPage p) _ = do
        setDocTitle p
        return NavState { currentPageId = p, sideMenuOpen = False }
    transform ToggleSideMenu s =
        return $ s { sideMenuOpen = not (sideMenuOpen s) } -- use a lens!

currentNavPageStore :: ReactStore NavState
currentNavPageStore = mkStore (NavState False Page1)

--------------------------------------------------------------------------
--- History API
--------------------------------------------------------------------------

foreign import javascript unsafe
    "window['history']['pushState']({page: $1}, $2)"
    js_historyPushState :: Int -> JSString -> IO ()

historyPushState :: NavPageId -> IO ()
historyPushState pid = do
    let (_, url) = pageTitleAndUrl pid
    js_historyPushState (fromEnum pid) $ JSString.pack url

foreign import javascript unsafe
    "document['title'] = $1;"
    js_setDocTitle :: JSString -> IO ()

setDocTitle :: NavPageId -> IO ()
setDocTitle pid = do
    let (title, _) = pageTitleAndUrl pid
    js_setDocTitle $ JSString.pack title

foreign import javascript unsafe
    "window['onpopstate'] = function(e) { $1(e['state'] ? e['state'].page : 0); };"
    js_setOnPopState :: Callback (JSRef -> IO ()) -> IO ()

initHistory :: IO ()
initHistory = do
    -- set the document title to match page1
    let (title, _) = pageTitleAndUrl Page1
    js_setDocTitle $ JSString.pack title

    -- register a callback for onpopstate event
    c <- syncCallback1 ContinueAsync $ \pageRef -> do
        pageInt <- fromMaybe (error "Unable to parse page") <$> fromJSRef pageRef
        alterStore currentNavPageStore $ BackToPage $ toEnum pageInt
    js_setOnPopState c