A Clojurescript library designed to help routing in the browser.
Include the dependency in your project.clj.
Then require the router.core
in your cljs files.
(ns your-namespace
(:require [router.core :as router :include-macros true]))
Define your routes with the match
macro. The syntax is of the secretary.
However, it is simplified in that it will accept only a value, and doesn’t pass parameters to the body.
(router/match "/" :root-path)
(router/match "/user/:id" :user-path)
(router/match "/admin/*path" :admin-path)
(router/match #"/search/([a-zA-Z]*)" :search-path)
Configure the history manager.
(router/config!)
Or, pass some options. The defaults are:
(router/config! {:use-fragment false ;; Use '/#/some/path' style pathes
:path-prefix "" ;; The path prefix. For example "!"
:enabled true}) ;; Enable the manager
Later on, you can enable or disable the manager with
(router/set-enabled!)
(router/set-disabled!)
And, finally you can register your callback to be called on routing.
(defn callback
"Callback should take two params:
value: The value you associated with the current path.
params: The returned parameters of current route from secretary."
[value params]
(pr "We are at" value "with params" params))
(router/set-callback! your-callback)
The current route is (router/current-route)
.
You can navigate to a new route with (router/navigate! "/new/route")
.
Also, you can prevent the default behaviour of links and buttons from refreshing the window with
(router/prevent-default-refresh!)
.
In the router.react
namespace resides a default callback which you can use to mount your pages to
specific DOM node.
(ns some-ns
(:require [router.core :refer-macros (match)]
[router.react :as rr]))
(match "/some-route" some-react-element)
(match "/other-route" (fn [p] construct-and-return-an-react-element))
(rr/initiate! (.-body js/document))
router.react
expects that the values you associated with the routes are either React elements or
functions of one parameter which return react elements. The parameter is params
of the route.
On page navigation the supplied React element will be rendered on the DOM node.
You can pass a custom callback to transform the event before it acts upon it, or do any other thing with it.
(defn optional-callback
"It must accept two params:
value: The assosiated value with the route
params: The route's params
And returns a vector of two: The transformed value and params."
[value params]
(pr "recieved" value params)
[value params])
The router.reagent
namespace provides a different match
macro to facilitate using Reagent
components.
(ns some-ns
(:require [router.reagent :refer-macros (match)]))
(match "/some-path" {:keys [param1] :as route-params} [reagent-component param1 param2 ...])
This macro delegates to the original router.core/match
macro and assosiates with the route a
function with the shape that callback of router.react
requires.
Dispatcher: System ----> Route Objects* Commiter: System <---- Route Object Routing Table: [ name: route object <-- assosiation --> route value , name: route object <-- assosiation --> route value , name: route object <-- assosiation --> route value , ... ]
Routing is a separate concern from application logic. By complecting routing with logic only a little can be obtained, and a lot will be lost as a result. It is better that we create a composable and flexible routing system which can be bent to different use cases, but by default it does the right thing: no logic in routing.
A router in its simplest form is a set of three things: Dispatcher, Commiter, Routing Table. Dispatcher and Commiter are system specific. For example, browser tailored, ring tailored, memcache tailored, etc. But Routing Table is application specific.
Usually the Dispatcher and Commiter are provided for you, and you only create a routing table. However, Dispatcher and Commiter are the points of composition. And they can provide the means to the infinite flexibility.
The dispatcher-commiter duo establish a one to one correspondence between the system state and Routing objects. The Dispatcher component listens to the system state and emits new Route objects on the system transition. And the commiter receives Route objects and transitions the system state to match the route object.
Now, the routing table is almost the same thing, if you’ve made dispatcher-commiter application specific and partial.
Each association in the Routing Table is a uniqely named bidirectional partial function between a system domain representation of state and an application domain representation of state. What this means is that if you pass a route object to an assosiation, it might return you an analogy of it in terms of the application. For example, for something like "api.example.com/v2/users/123"
, an association named users
might return an object JohnDoe
. And the other direction must work as well. Which means, association users
must uniqely map JohnDoe
to the route object "api.example.com/v2/users/123"
.
-
Make it isomorphic and integrate ideas from https://github.com/mkhoeini/trouter .
-
Make router irrelivant or a separate concern by integrating it with https://github.com/mkhoeini/dispatcher .