|
1 | 1 | # purescript-web-router |
2 | 2 |
|
3 | | -A basic pushstate router for React with support for asynchronous routing logic, built using [react-basic-hooks](https://github.com/spicydonuts/purescript-react-basic-hooks). I recommend [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex) for easy parsing and printing. |
4 | | - |
5 | | -For a basic example see [here](https://github.com/robertdp/purescript-web-router-example/tree/master/src). |
6 | | - |
7 | | -## How to use with Spago |
8 | | - |
9 | | -Add `web-router` to your `packages.dhall`: |
10 | | - |
11 | | -```dhall |
12 | | -let additions = |
13 | | - { web-router = |
14 | | - { dependencies = |
15 | | - [ "aff" |
16 | | - , "effect" |
17 | | - , "freet" |
18 | | - , "indexed-monad" |
19 | | - , "prelude" |
20 | | - , "profunctor-lenses" |
21 | | - , "routing" |
22 | | - ] |
23 | | - , repo = "https://github.com/robertdp/purescript-web-router.git" |
24 | | - , version = "v0.3.0" |
| 3 | +A router for browsers that supports asynchronous routing logic. Bring your own printing and parsing (check out [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex)). |
| 4 | + |
| 5 | +For a basic React example see [here](https://github.com/robertdp/purescript-web-router-example/tree/master/src). |
| 6 | + |
| 7 | +## How to use |
| 8 | + |
| 9 | +### 1. Install with Spago |
| 10 | + |
| 11 | +`$ spago install web-router` (coming soon) |
| 12 | + |
| 13 | +### 2. Define your routes |
| 14 | + |
| 15 | +```purescript |
| 16 | +data Route |
| 17 | + = Page Page |
| 18 | + | NotFound |
| 19 | +
|
| 20 | +data Page |
| 21 | + = Home |
| 22 | + | ProductList |
| 23 | + | ProductView ProductId |
| 24 | + | About |
| 25 | + | ContactUs |
| 26 | +
|
| 27 | +type ProductId = Int |
| 28 | +``` |
| 29 | + |
| 30 | +### 3. Implement parsing and printing |
| 31 | + |
| 32 | +This example uses [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex). |
| 33 | + |
| 34 | +<details> |
| 35 | +<summary>Imports</summary> |
| 36 | + |
| 37 | +```purescript |
| 38 | +import Prelude hiding ((/)) |
| 39 | +import Data.Either (Either) |
| 40 | +import Data.Generic.Rep (class Generic) |
| 41 | +import Routing.Duplex (RouteDuplex', default, end, int, parse, print, root, segment) |
| 42 | +import Routing.Duplex.Generic (noArgs, sum) |
| 43 | +import Routing.Duplex.Generic.Syntax ((/)) |
| 44 | +import Routing.Duplex.Parser (RouteError) |
| 45 | +``` |
| 46 | + |
| 47 | +</details> |
| 48 | + |
| 49 | +```purescript |
| 50 | +derive instance Generic Route _ |
| 51 | +derive instance Generic Page _ |
| 52 | +
|
| 53 | +productId :: RouteDuplex' ProductId |
| 54 | +productId = int segment |
| 55 | +
|
| 56 | +routes :: RouteDuplex' Route |
| 57 | +routes = |
| 58 | + default NotFound $ |
| 59 | + sum |
| 60 | + { "Page": pages |
| 61 | + , "NotFound": "404" / noArgs |
| 62 | + } |
| 63 | +
|
| 64 | +pages :: RouteDuplex' Page |
| 65 | +pages = |
| 66 | + root $ end $ |
| 67 | + sum |
| 68 | + { "Home": noArgs |
| 69 | + , "ProductList": "products" / noArgs |
| 70 | + , "ProductView": "products" / productId |
| 71 | + , "About": "about" / noArgs |
| 72 | + , "ContactUs": "about" / noArgs |
25 | 73 | } |
26 | | - } |
| 74 | +
|
| 75 | +-- | This is route parser we need to pass to the driver. |
| 76 | +-- | It can produce any route which allows the parser to return a value of `NotFound` instead of failing. |
| 77 | +parseRoute :: forall String -> Either RouteError Route |
| 78 | +parseRoute = parse routes |
| 79 | +
|
| 80 | +-- | This is the route printer we need to pass to the driver. |
| 81 | +-- | It can only print paths to valid pages, which means a path can't be produced for the `NotFound` route. |
| 82 | +-- | With this approach routes can be seperated based on whether they should be a navigation target and have a URL. |
| 83 | +-- | Note: assymetry is not required, and a symmetrical printer works as well. |
| 84 | +printRoute :: Page -> String |
| 85 | +printRoute = print pages |
| 86 | +``` |
| 87 | + |
| 88 | +### Define how your application reacts to navigation and routing events |
| 89 | + |
| 90 | +<details> |
| 91 | +<summary>Imports</summary> |
| 92 | + |
| 93 | +```purescript |
| 94 | +import Web.Router as Router |
| 95 | +``` |
| 96 | + |
| 97 | +</details> |
| 98 | + |
| 99 | +```purescript |
| 100 | +onNavigation :: Maybe Route -> Route -> Router.RouterM Route Page Router.Routing Router.Resolved Unit |
| 101 | +onNavigation previousRoute requestedRoute = |
| 102 | + case requestedRoute of |
| 103 | + NotFound -> |
| 104 | + case previousRoute of |
| 105 | + Just (Page page) -> Router.do |
| 106 | + liftEffect showBrokenNavigationMessage |
| 107 | + Router.redirect page -- redirect back to the previous page and show a message |
| 108 | + _ -> |
| 109 | + Router.continue -- no previous page, so just show the "not found" page |
| 110 | + _ -> Router.do |
| 111 | + access <- liftAff fetchUserAccess |
| 112 | + if userHasAccess requestedRoute access then |
| 113 | + Router.continue -- they have access, so resolve with the requested page |
| 114 | + else |
| 115 | + Router.override NotFound -- no access, so pretend the page doesn't exist |
| 116 | +
|
| 117 | +
|
| 118 | +onEvent :: Router.RoutingEvent Route -> Effect Unit |
| 119 | +onEvent newEvent = |
| 120 | + case newEvent of |
| 121 | + Router.Routing previousRoute requestedRoute -> |
| 122 | + showNavigationSpinner |
| 123 | + Router.Resolved previousRoute newRoute -> |
| 124 | + hideNavigationSpinner |
| 125 | + setCurrentRoute newRoute |
| 126 | +``` |
| 127 | + |
| 128 | +### Connect up the driver and router |
| 129 | + |
| 130 | +<details> |
| 131 | +<summary>Imports</summary> |
| 132 | + |
| 133 | +```purescript |
| 134 | +import Web.Router as Router |
| 135 | +import Web.Router.PushState as PushState |
| 136 | +``` |
| 137 | + |
| 138 | +</details> |
| 139 | + |
| 140 | +```purescript |
| 141 | +mkRouter :: Effect (Router.Router Route Page) |
| 142 | +mkRouter = do |
| 143 | + driver <- PushState.mkInterface parseRoute printRoute |
| 144 | + router <- Router.mkInterface onNavigation onEvent driver |
| 145 | + pure router |
27 | 146 | ``` |
28 | 147 |
|
29 | | -And then install with |
30 | | -`$ spago install web-router` |
| 148 | +Both pushstate and hash drivers are included, or a custom driver can be implemented. An example of a custom driver could be one that synchronises some navigation state over sockets, for an experience where one user's behaviour could be broadcast to multiple users to follow along. |
0 commit comments