git.iko.soy/typeable/reflex-dom-nested-routing
on master help sign in
6 files changed+161−26
since splitsnapshots
unifiedsplit
CHANGELOG.md+13 LICENSE+30 README.md+4−1 Reflex/Dom/NestedRoute.hs → Reflex/Dom/Routing/Nested.hs+41−13 Reflex/Dom/RouteWriter.hs → Reflex/Dom/Routing/Writer.hs+30−12 reflex-dom-nested-routing.cabal+43
CHANGELOG.mdnew file+13
@@ -0,0 +1,13 @@
1+# Change Log
2+
3+## 0.0.3
4+
5+ * **[Breaking change]** Reorganize modules.
6+
7+## 0.0.2
8+
9+ * **[Breaking change]** Fix `parentRouteSegments` which was giving "grandparent" route instead of parent.
10+
11+## 0.0.1
12+
13+ * Initial prototype.
LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2017 Elliot Cameron
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Elliot Cameron nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md+4−1
@@ -1,5 +1,8 @@
11# Nested Routing for Reflex-DOM
22
3+[![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)](http://www.haskell.org)
4+[![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)](https://tldrlegal.com/license/bsd-3-clause-license-%28revised%29)
5+
36Example:
47
58```haskell
@@ -18,7 +21,7 @@ app = runRouteWithPathInFragment $ do
1821```haskell
1922app = runRouteWithPathInFragment $ fmap snd $ runRouteWriterT $ do
2023 void $ withRoute $ \route -> case fromMaybe "" route of
21− "" -> tellRouteAs ["users"] <=< buttonClick "Open users"
24+ "" -> tellRouteAs ["users"] =<< buttonClick "Open users"
2225 "users" -> users
2326 "test" -> codeToRun
2427 "settings" -> text "Settings"
Reflex/Dom/NestedRoute.hs → Reflex/Dom/Routing/Nested.hsrenamed+41−13
@@ -7,10 +7,11 @@
77{-# LANGUAGE RankNTypes #-}
88{-# LANGUAGE RecursiveDo #-}
99{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE StandaloneDeriving #-}
1011{-# LANGUAGE TypeFamilies #-}
1112{-# LANGUAGE UndecidableInstances #-}
1213
13−module Reflex.Dom.NestedRoute where
14+module Reflex.Dom.Routing.Nested where
1415
1516import Control.Lens (Rewrapped, Wrapped (..), iso, to,
1617 (^.))
@@ -31,7 +32,6 @@ import Data.Maybe (listToMaybe)
3132import Data.Text (Text)
3233import qualified Data.Text as T
3334import Data.Text.Encoding (decodeUtf8, encodeUtf8)
34−import Foreign.JavaScript.TH
3535import Language.Javascript.JSaddle (MonadJSM)
3636import Reflex
3737import Reflex.Dom.Builder.Class
@@ -113,6 +113,8 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RouteT t s
113113 placeRawElement = lift . placeRawElement
114114 wrapRawElement e = lift . wrapRawElement e
115115
116+instance (Monad m, NotReady t m) => NotReady t (RouteT t segment m)
117+
116118instance MonadReader r m => MonadReader r (RouteT t segment m) where
117119 ask = lift ask
118120 local f (RouteT a) = RouteT $ mapReaderT (local f) a
@@ -126,14 +128,25 @@ instance HasRoute t segment m => HasRoute t segment (EventWriterT t w m) where
126128instance EventWriter t w m => EventWriter t w (RouteT t segment m) where
127129 tellEvent = lift . tellEvent
128130
131+instance HasRoute t segment m => HasRoute t segment (DynamicWriterT t w m) where
132+ routeContext = lift routeContext
133+ withSegments f (DynamicWriterT a) = DynamicWriterT $ StrictState.mapStateT (withSegments f) a
134+
135+instance DynamicWriter t w m => DynamicWriter t w (RouteT t segment m) where
136+ tellDyn = lift . tellDyn
137+
129138instance HasDocument m => HasDocument (RouteT t segment m)
130−instance HasJSContext m => HasJSContext (RouteT t segment m) where
131− type JSContextPhantom (RouteT t segment m) = JSContextPhantom m
132− askJSContext = RouteT askJSContext
139+
133140#ifndef ghcjs_HOST_OS
134141instance MonadJSM m => MonadJSM (RouteT t segment m)
135142#endif
136143
144+deriving instance DomRenderHook t m => DomRenderHook t (RouteT t segment m)
145+
146+instance (Prerender t m, Monad m, Reflex t) => Prerender t (RouteT t segment m) where
147+ type Client (RouteT t segment m) = RouteT t segment (Client m)
148+ prerender (RouteT a) (RouteT b) = RouteT $ prerender a b
149+
137150
138151instance PrimMonad m => PrimMonad (RouteT t segment m) where
139152 type PrimState (RouteT t segment m) = PrimState m
@@ -142,7 +155,9 @@ instance PrimMonad m => PrimMonad (RouteT t segment m) where
142155--instance HasMountStatus t m => HasMountStatus t (RouteT segment r m) where
143156-- getMountStatus = lift getMountStatus
144157
145−
158+-- | Runs a monadic action (in 'RouteT') over the current URL as "interpreted"
159+-- by the given URL-parser. The action must return an 'Event' which can trigger
160+-- the route to change.
146161runRoute :: forall segment t m. (MonadWidget t m, Eq segment)
147162 => (forall a. URIRef a -> [segment])
148163 -> (forall a. URIRef a -> [segment] -> URIRef a)
@@ -170,16 +185,21 @@ runRoute toSegments fromSegments (RouteT f) = do
170185 let x = ffor uri $ \uri' -> fromSegments uri' <$> newSegments
171186 pure (switch (current x))
172187
173−
188+-- | A very simple version of 'runRoute' that parses only the URL fragment
189+-- and splits it over @/@. Each path segment is therefore nothing more than 'Text'.
174190runRouteWithPathInFragment
175191 :: forall t m. (MonadWidget t m)
176192 => RouteT t Text m (Event t [Text])
177193 -> m ()
178194runRouteWithPathInFragment = runRoute
179− (T.splitOn "/" . T.dropAround (=='/') . fragAsText)
195+ (nullTextToEmptyList . T.splitOn "/" . T.dropAround (=='/') . fragAsText)
180196 (\oldUrl -> setFrag oldUrl . T.intercalate "/")
197+ where
198+ nullTextToEmptyList [""] = []
199+ nullTextToEmptyList x = x
181200
182−
201+-- | Introduces a new "layer" in the nested routing tree. The given function takes
202+-- the current layer's route segment and builds the DOM for that segment.
183203withRoute
184204 :: forall a segment t m. (DomBuilder t m, MonadFix m, PostBuild t m, MonadHold t m, HasRoute t segment m, Eq segment)
185205 => (Maybe segment -> m a)
@@ -218,25 +238,33 @@ withRoute f = do
218238
219239 switchPromptly never =<< dyn component
220240
221−
241+-- | All routing segments in the current URL.
222242allRouteSegments :: (MonadHold t m, MonadFix m, HasRoute t segment m, Eq segment) => m (Dynamic t [segment])
223243allRouteSegments = _routeContext_allSegments <$> routeContext
224244
245+-- | The routing segment at "this layer" in the tree. "This layer" is defined by how many
246+-- nested 'withRoute's exist above the caller of this function.
225247currentRouteSegment :: (Functor m, HasRoute t segment m) => m (Dynamic t (Maybe segment))
226248currentRouteSegment = _routeContext_currentSegment <$> routeContext
227249
250+-- | The next layer's segment in the tree. Like 'withRoute' this can be used to switch
251+-- over route segments, but it does not place a new layer in the tree.
228252nextRouteSegment :: (MonadHold t m, MonadFix m, HasRoute t segment m, Eq segment) => m (Dynamic t (Maybe segment))
229253nextRouteSegment = do
230254 ctx <- routeContext
231255 holdUniqDyn (listToMaybe <$> _routeContext_nextSegments ctx)
232256
257+-- | Route segments from parent layers.
233258parentRouteSegments :: (MonadHold t m, MonadFix m, HasRoute t segment m, Eq segment) => m (Dynamic t [segment])
234259parentRouteSegments = do
235260 ctx <- routeContext
236− holdUniqDyn $ do
237− allSegments <- _routeContext_allSegments ctx
238− pure $ take (_routeContext_currentDepth ctx - 1) allSegments
261+ holdUniqDyn $ take (_routeContext_currentDepth ctx) <$> _routeContext_allSegments ctx
262+
263+-- | Route segments from child layers.
264+childRouteSegments :: (MonadHold t m, MonadFix m, HasRoute t segment m, Eq segment) => m (Dynamic t [segment])
265+childRouteSegments = holdUniqDyn =<< _routeContext_nextSegments <$> routeContext
239266
267+-- | A simple helper that produces a routing event on 'getPostBuild'.
240268redirectLocally :: (PostBuild t m) => [segment] -> m (Event t [segment])
241269redirectLocally segments = (segments <$) <$> getPostBuild
242270
Reflex/Dom/RouteWriter.hs → Reflex/Dom/Routing/Writer.hsrenamed+30−12
@@ -5,20 +5,21 @@
55{-# LANGUAGE MultiParamTypeClasses #-}
66{-# LANGUAGE RankNTypes #-}
77{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE StandaloneDeriving #-}
89{-# LANGUAGE TypeFamilies #-}
910{-# LANGUAGE UndecidableInstances #-}
1011
11−module Reflex.Dom.RouteWriter where
12+module Reflex.Dom.Routing.Writer where
1213
13−import Control.Lens (Rewrapped, Wrapped(..), iso)
14−import Control.Monad.Exception (MonadAsyncException, MonadException)
14+import Control.Lens (Rewrapped, Wrapped (..), iso)
15+import Control.Monad.Exception (MonadAsyncException,
16+ MonadException)
1517import Control.Monad.Fix
1618import Control.Monad.Primitive (PrimMonad, PrimState, primitive)
1719import Control.Monad.Reader
1820import Control.Monad.Ref
1921import qualified Control.Monad.State.Strict as StrictState
2022import Data.Coerce (coerce)
21−import Foreign.JavaScript.TH
2223import Language.Javascript.JSaddle (MonadJSM)
2324import Reflex
2425import Reflex.Dom.Builder.Class
@@ -26,14 +27,12 @@ import Reflex.Dom.Builder.Immediate
2627import Reflex.Dom.Core
2728import Reflex.Host.Class
2829
29−import Reflex.Dom.NestedRoute
30−
30+import Reflex.Dom.Routing.Nested
3131
3232
3333class (Reflex t, Monad m) => RouteWriter t segment m | m -> segment, m -> t where
3434 tellRoute :: Event t [segment] -> m ()
3535
36−
3736newtype RouteWriterT t segment m a = RouteWriterT { unRouteWriterT :: EventWriterT t [segment] m a }
3837 deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadHold t,
3938 MonadSample t, MonadAsyncException, MonadException, MonadTrans, PostBuild t,
@@ -43,6 +42,11 @@ newtype RouteWriterT t segment m a = RouteWriterT { unRouteWriterT :: EventWrite
4342instance (MonadWidget t m) => RouteWriter t segment (RouteWriterT t segment m) where
4443 tellRoute = RouteWriterT . tellEvent
4544
45+deriving instance DomRenderHook t m => DomRenderHook t (RouteWriterT t segment m)
46+
47+instance (Prerender t m, Monad m, Reflex t) => Prerender t (RouteWriterT t segment m) where
48+ type Client (RouteWriterT t segment m) = RouteWriterT t segment (Client m)
49+ prerender (RouteWriterT a) (RouteWriterT b) = RouteWriterT $ prerender a b
4650
4751instance Requester t m => Requester t (RouteWriterT t segment m) where
4852 type Request (RouteWriterT t segment m) = Request m
@@ -75,7 +79,7 @@ instance MonadRef m => MonadRef (RouteWriterT t segment m) where
7579 {-# INLINABLE writeRef #-}
7680 writeRef r = lift . writeRef r
7781
78−instance (MonadAdjust t m, MonadHold t m) => MonadAdjust t (RouteWriterT t segment m) where
82+instance (Adjustable t m, MonadHold t m) => Adjustable t (RouteWriterT t segment m) where
7983 runWithReplace a0 a' = RouteWriterT $ runWithReplace (unRouteWriterT a0) (fmapCheap unRouteWriterT a')
8084 traverseDMapWithKeyWithAdjust f dm edm = RouteWriterT $ traverseDMapWithKeyWithAdjust (\k v -> unRouteWriterT $ f k v) (coerce dm) (coerceEvent edm)
8185 {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
@@ -91,18 +95,22 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RouteWrite
9195 placeRawElement = lift . placeRawElement
9296 wrapRawElement e = lift . wrapRawElement e
9397
98+instance (Monad m, NotReady t m) => NotReady t (RouteWriterT t segment m)
99+
94100deriving instance MonadReader r m => MonadReader r (RouteWriterT t segment m)
95101
96102instance RouteWriter t segment m => RouteWriter t segment (EventWriterT t w m) where
97103 tellRoute = lift . tellRoute
98−
99104instance EventWriter t w m => EventWriter t w (RouteWriterT t segment m) where
100105 tellEvent = lift . tellEvent
101106
107+instance RouteWriter t segment m => RouteWriter t segment (DynamicWriterT t w m) where
108+ tellRoute = lift . tellRoute
109+instance DynamicWriter t w m => DynamicWriter t w (RouteWriterT t segment m) where
110+ tellDyn = lift . tellDyn
111+
112+
102113instance HasDocument m => HasDocument (RouteWriterT t segment m)
103−instance HasJSContext m => HasJSContext (RouteWriterT t segment m) where
104− type JSContextPhantom (RouteWriterT t segment m) = JSContextPhantom m
105− askJSContext = RouteWriterT askJSContext
106114#ifndef ghcjs_HOST_OS
107115instance MonadJSM m => MonadJSM (RouteWriterT segment t m)
108116#endif
@@ -125,6 +133,10 @@ runRouteWriterT (RouteWriterT m) = runEventWriterT m
125133tellRedirectLocally :: (PostBuild t m, RouteWriter t segment m) => [segment] -> m ()
126134tellRedirectLocally segments = tellRoute =<< redirectLocally segments
127135
136+-- | Alias for 'tellRedirectLocally'
137+localRedirect :: (PostBuild t m, RouteWriter t segment m) => [segment] -> m ()
138+localRedirect = tellRedirectLocally
139+
128140
129141-- | Sets a new route to the given route whenever the given 'Event' fires, ignoring the 'Event' payload.
130142tellRouteAs :: (RouteWriter t segment m) => [segment] -> Event t a -> m ()
@@ -149,3 +161,9 @@ tellRouteRelative
149161tellRouteRelative ev = do
150162 parents <- parentRouteSegments
151163 tellRoute $ attachWith (++) (current parents) ev
164+
165+-- Like 'tellRouteAs' and 'tellRouteRelative'.
166+tellRouteRelativeAs
167+ :: (RouteWriter t segment m, HasRoute t segment m, MonadHold t m, MonadFix m, Eq segment)
168+ => [segment] -> Event t a -> m ()
169+tellRouteRelativeAs segments ev = tellRouteRelative (segments <$ ev)
reflex-dom-nested-routing.cabalnew file+43
@@ -0,0 +1,43 @@
1+name: reflex-dom-nested-routing
2+version: 0.0.3
3+synopsis: Nested client-side routing for Reflex-DOM
4+description: See README at <https://github.com/3noch/reflex-dom-nested-routing#readme>
5+category: Web
6+maintainer: Elliot Cameron <elliot@graftedin.io>
7+copyright: 2017 Elliot Cameron
8+license: BSD3
9+license-file: LICENSE
10+build-type: Simple
11+cabal-version: >= 1.10
12+extra-source-files:
13+ CHANGELOG.md
14+ README.md
15+
16+
17+library
18+ hs-source-dirs: .
19+
20+ ghc-options:
21+ -Wall
22+ -Wincomplete-uni-patterns
23+ -Wincomplete-record-updates
24+ -Wno-unused-do-bind
25+ -O2
26+ build-depends:
27+ base
28+ , bytestring
29+ , exception-transformers
30+ , jsaddle
31+ , lens
32+ , mtl
33+ , primitive
34+ , ref-tf
35+ , reflex
36+ , reflex-dom-core
37+ , reflex-dom-contrib
38+ , text
39+ , uri-bytestring
40+ exposed-modules:
41+ Reflex.Dom.Routing.Nested
42+ Reflex.Dom.Routing.Writer
43+ default-language: Haskell2010