@@ -1,8 +1,8 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE MultiParamTypeClasses #-}
3{-# LANGUAGE MultiParamTypeClasses #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
6module Reflex.Dom.Location
6module Reflex.Dom.Location
7 ( browserHistoryWith
7 ( browserHistoryWith
8 , getLocationAfterHost
8 , getLocationAfterHost
@@ -11,6 +11,11 @@ module Reflex.Dom.Location
11 , getLocationPath
11 , getLocationPath
12 , getLocationProtocol
12 , getLocationProtocol
13 , getLocationUrl
13 , getLocationUrl
15+ , HistoryCommand (..)
16+ , HistoryStateUpdate (..)
16import Reflex
21import Reflex
@@ -18,16 +23,19 @@ import Reflex.Dom.Builder.Immediate (wrapDomEvent)
19import Control.Lens ((^.))
24import Control.Lens ((^.))
20import Control.Monad ((>=>))
25import Control.Monad ((>=>))
21import Data.Text (Text)
27import Data.Text (Text)
22import qualified GHCJS.DOM as DOM
28import qualified GHCJS.DOM as DOM
23import qualified GHCJS.DOM.EventM as DOM
29import qualified GHCJS.DOM.EventM as DOM
24import qualified GHCJS.DOM.Location as Location
30import qualified GHCJS.DOM.Location as Location
31+import qualified GHCJS.DOM.History as History
32+import qualified GHCJS.DOM.PopStateEvent as PopStateEvent
25−import GHCJS.DOM.Types (Location)
33+import GHCJS.DOM.Types (Location, History, SerializedScriptValue (..), liftJSM)
26import qualified GHCJS.DOM.Types as DOM
34import qualified GHCJS.DOM.Types as DOM
27import qualified GHCJS.DOM.Window as Window
35import qualified GHCJS.DOM.Window as Window
28import qualified GHCJS.DOM.WindowEventHandlers as DOM
36import qualified GHCJS.DOM.WindowEventHandlers as DOM
29−import Language.Javascript.JSaddle (FromJSString, MonadJSM, ToJSString, fromJSValUnchecked, js1, toJSVal)
37+import Language.Javascript.JSaddle (FromJSString, MonadJSM, ToJSString, fromJSValUnchecked, js1, ToJSVal (..), FromJSVal (..))
32withLocation :: (MonadJSM m) => (Location -> m a) -> m a
40withLocation :: (MonadJSM m) => (Location -> m a) -> m a
33withLocation f = DOM.currentWindowUnchecked >>= Window.getLocation >>= f
41withLocation f = DOM.currentWindowUnchecked >>= Window.getLocation >>= f
@@ -84,3 +92,70 @@ browserHistoryWith f = do
84 loc0 <- f location
92 loc0 <- f location
85 locEv <- wrapDomEvent window (`DOM.on` DOM.popState) $ f location
93 locEv <- wrapDomEvent window (`DOM.on` DOM.popState) $ f location
86 holdDyn loc0 locEv
94 holdDyn loc0 locEv
96+--TODO: Pending https://github.com/haskell/network-uri/issues/39, ensure that
97+--we're handling escaping of URIs correctly
98+data HistoryItem = HistoryItem
99+ { _historyItem_state :: SerializedScriptValue
100+ , _historyItem_uri :: URI
101+ -- ^ NOTE: All URIs in this module are assumed to be already percent-escaped
104+data HistoryStateUpdate = HistoryStateUpdate
105+ { _historyStateUpdate_state :: SerializedScriptValue
106+ , _historyStateUpdate_title :: Text
107+ , _historyStateUpdate_uri :: Maybe URI
108+ -- ^ If Just, update the URI; otherwise leave it unchanged
109+ -- NOTE: All URIs in this module are assumed to be already percent-escaped
113+ = HistoryCommand_PushState HistoryStateUpdate
114+ | HistoryCommand_ReplaceState HistoryStateUpdate
116+runHistoryCommand :: MonadJSM m => History -> HistoryCommand -> m ()
117+runHistoryCommand history = \case
118+ HistoryCommand_PushState su -> History.pushState history
119+ (_historyStateUpdate_state su)
120+ (_historyStateUpdate_title su)
121+ (show <$> _historyStateUpdate_uri su)
122+ HistoryCommand_ReplaceState su -> History.replaceState history
123+ (_historyStateUpdate_state su)
124+ (_historyStateUpdate_title su)
125+ (show <$> _historyStateUpdate_uri su)
127+getLocationUriAuth :: MonadJSM m => Location -> m URIAuth
128+getLocationUriAuth location = URIAuth "" -- Username and password don't seem to be available in most browsers
129+ <$> Location.getHostname location
130+ <*> (appendColonIfNotEmpty <$> Location.getPort location)
131+ where appendColonIfNotEmpty = \case
135+getLocationUri :: MonadJSM m => Location -> m URI
136+getLocationUri location = URI
137+ <$> Location.getProtocol location
138+ <*> (Just <$> getLocationUriAuth location)
139+ <*> Location.getPathname location
140+ <*> Location.getSearch location
141+ <*> Location.getHash location
143+manageHistory :: (MonadJSM m, TriggerEvent t m, MonadHold t m, PerformEvent t m, MonadJSM (Performable m)) => Event t HistoryCommand -> m (Dynamic t HistoryItem)
144+manageHistory runCmd = do
145+ window <- DOM.currentWindowUnchecked
146+ location <- Window.getLocation window
147+ history <- Window.getHistory window
148+ let getCurrentHistoryItem = HistoryItem
149+ <$> History.getState history
150+ <*> getLocationUri location
151+ item0 <- liftJSM getCurrentHistoryItem
152+ itemSetInternal <- performEvent $ ffor runCmd $ \cmd -> liftJSM $ do
153+ runHistoryCommand history cmd
154+ getCurrentHistoryItem
155+ itemSetExternal <- wrapDomEvent window (`DOM.on` DOM.popState) $ do
158+ <$> (SerializedScriptValue <$> PopStateEvent.getState e)
159+ <*> getLocationUri location
160+ holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
161+--TODO: Handle title setting better