mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 17:01:11 +00:00
Implement navigate and set-query functions
This commit is contained in:
parent
f78116e346
commit
48bbdba8ed
5 changed files with 167 additions and 46 deletions
|
|
@ -12,7 +12,7 @@
|
||||||
(vec vs))))
|
(vec vs))))
|
||||||
|
|
||||||
(defn query-params
|
(defn query-params
|
||||||
"Given goog.Uri, read query parameters into Clojure map."
|
"Given goog.Uri, read query parameters into a Clojure map."
|
||||||
[^Uri uri]
|
[^Uri uri]
|
||||||
(let [q (.getQueryData uri)]
|
(let [q (.getQueryData uri)]
|
||||||
(->> q
|
(->> q
|
||||||
|
|
@ -20,6 +20,19 @@
|
||||||
(map (juxt keyword #(query-param q %)))
|
(map (juxt keyword #(query-param q %)))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
|
(defn set-query-params
|
||||||
|
"Given Reitit-frontend path, update the query params
|
||||||
|
with given function and arguments.
|
||||||
|
|
||||||
|
Note: coercion is not applied to the query params"
|
||||||
|
[path new-query-or-update-fn]
|
||||||
|
(let [^goog.Uri uri (Uri/parse path)
|
||||||
|
new-query (if (fn? new-query-or-update-fn)
|
||||||
|
(new-query-or-update-fn (query-params uri))
|
||||||
|
new-query-or-update-fn)]
|
||||||
|
(.setQueryData uri (QueryData/createFromMap (clj->js new-query)))
|
||||||
|
(.toString uri)))
|
||||||
|
|
||||||
(defn match-by-path
|
(defn match-by-path
|
||||||
"Given routing tree and current path, return match with possibly
|
"Given routing tree and current path, return match with possibly
|
||||||
coerced parameters. Return nil if no match found.
|
coerced parameters. Return nil if no match found.
|
||||||
|
|
@ -88,14 +101,3 @@
|
||||||
match)
|
match)
|
||||||
(do (js/console.warn "missing route" name)
|
(do (js/console.warn "missing route" name)
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(defn update-path-query-params
|
|
||||||
"Given Reitit-frontend path, update the query params
|
|
||||||
with given function and arguments.
|
|
||||||
|
|
||||||
NOTE: coercion is not applied to the query params"
|
|
||||||
[path f & args]
|
|
||||||
(let [^goog.Uri uri (Uri/parse path)
|
|
||||||
new-query (apply f (query-params uri) args)]
|
|
||||||
(.setQueryData uri (QueryData/createFromMap (clj->js new-query)))
|
|
||||||
(.toString uri)))
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
"Easy wrapper over reitit.frontend.history,
|
"Easy wrapper over reitit.frontend.history,
|
||||||
handling the state. Only one router can be active
|
handling the state. Only one router can be active
|
||||||
at a time."
|
at a time."
|
||||||
(:require [reitit.frontend.history :as rfh]
|
(:require [reitit.frontend.history :as rfh]))
|
||||||
[reitit.frontend :as rf]))
|
|
||||||
|
|
||||||
(defonce history (atom nil))
|
(defonce history (atom nil))
|
||||||
|
|
||||||
|
|
@ -103,14 +102,43 @@
|
||||||
([name path-params query-params]
|
([name path-params query-params]
|
||||||
(rfh/replace-state @history name path-params query-params)))
|
(rfh/replace-state @history name path-params query-params)))
|
||||||
|
|
||||||
(defn update-query
|
;; This duplicates previous two, but the map parameter will be easier way to
|
||||||
;; TODO: Sync the docstring with other namespaces
|
;; extend the functions, e.g. to work with fragment string. Toggling push vs
|
||||||
"Takes the current location and updates the query params
|
;; replace can be also simpler with a flag.
|
||||||
with given fn and arguments."
|
;; Navigate and set-query are also similer to react-router API.
|
||||||
[f & args]
|
(defn
|
||||||
;; TODO: rfh version?
|
^{:see-also ["reitit.frontend.history/navigate"]}
|
||||||
(let [current-path (rfh/-get-path @history)
|
navigate
|
||||||
new-path (apply rf/update-path-query-params current-path f args)]
|
"Updates the browser location and either pushes new entry to the history stack
|
||||||
;; TODO: replaceState version
|
or replaces the latest entry in the the history stack (controlled by
|
||||||
(.pushState js/window.history nil "" new-path)
|
`replace` option) using URL built from a route defined by name given
|
||||||
(rfh/-on-navigate @history new-path)))
|
parameters.
|
||||||
|
|
||||||
|
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||||
|
|
||||||
|
Note: currently collections in query-parameters are encoded as field-value
|
||||||
|
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||||
|
differently, convert the collections to strings first.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||||
|
([name]
|
||||||
|
(rfh/navigate @history name))
|
||||||
|
([name {:keys [path-params query-params replace] :as opts}]
|
||||||
|
(rfh/navigate @history name opts)))
|
||||||
|
|
||||||
|
(defn
|
||||||
|
^{:see-also ["reitit.frontend.history/set-query"]}
|
||||||
|
set-query
|
||||||
|
"Update query parameters for the current route.
|
||||||
|
|
||||||
|
New query params can be given as a map, or a function taking
|
||||||
|
the old params and returning the new modified params.
|
||||||
|
|
||||||
|
Note: The query parameter values aren't coereced, so the
|
||||||
|
update fn will see string values for all query params."
|
||||||
|
([new-query-or-update-fn]
|
||||||
|
(rfh/set-query @history new-query-or-update-fn))
|
||||||
|
([new-query-or-update-fn {:keys [replace] :as opts}]
|
||||||
|
(rfh/set-query @history new-query-or-update-fn opts)))
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
(defprotocol History
|
(defprotocol History
|
||||||
(-init [this] "Create event listeners")
|
(-init [this] "Create event listeners")
|
||||||
(-stop [this] "Remove event listeners")
|
(-stop [this] "Remove event listeners")
|
||||||
(-on-navigate [this path])
|
(-on-navigate [this path] "Find a match for current routing path and call on-navigate callback")
|
||||||
(-get-path [this])
|
(-get-path [this] "Get the current routing path")
|
||||||
(-href [this path]))
|
(-href [this path] "Converts given routing path to browser location"))
|
||||||
|
|
||||||
;; This version listens for both pop-state and hash-change for
|
;; This version listens for both pop-state and hash-change for
|
||||||
;; compatibility for old browsers not supporting History API.
|
;; compatibility for old browsers not supporting History API.
|
||||||
|
|
@ -177,7 +177,9 @@
|
||||||
(if history
|
(if history
|
||||||
(-stop history)))
|
(-stop history)))
|
||||||
|
|
||||||
(defn href
|
(defn
|
||||||
|
^{:see-also ["reitit.core/match->path"]}
|
||||||
|
href
|
||||||
"Generate a URL for a route defined by name, with given path-params and query-params.
|
"Generate a URL for a route defined by name, with given path-params and query-params.
|
||||||
|
|
||||||
The URL is formatted using Reitit frontend history handler, so using it with
|
The URL is formatted using Reitit frontend history handler, so using it with
|
||||||
|
|
@ -219,7 +221,9 @@
|
||||||
(.pushState js/window.history nil "" (-href history path))
|
(.pushState js/window.history nil "" (-href history path))
|
||||||
(-on-navigate history path))))
|
(-on-navigate history path))))
|
||||||
|
|
||||||
(defn replace-state
|
(defn
|
||||||
|
^{:see-also ["reitit.core/match->path"]}
|
||||||
|
replace-state
|
||||||
"Updates the browser location and replaces latest entry in the history stack
|
"Updates the browser location and replaces latest entry in the history stack
|
||||||
using URL built from a route defined by name, with given path-params and
|
using URL built from a route defined by name, with given path-params and
|
||||||
query-params.
|
query-params.
|
||||||
|
|
@ -241,3 +245,50 @@
|
||||||
path (reitit/match->path match query-params)]
|
path (reitit/match->path match query-params)]
|
||||||
(.replaceState js/window.history nil "" (-href history path))
|
(.replaceState js/window.history nil "" (-href history path))
|
||||||
(-on-navigate history path))))
|
(-on-navigate history path))))
|
||||||
|
|
||||||
|
(defn
|
||||||
|
^{:see-also ["reitit.core/match->path"]}
|
||||||
|
navigate
|
||||||
|
"Updates the browser location and either pushes new entry to the history stack
|
||||||
|
or replaces the latest entry in the the history stack (controlled by
|
||||||
|
`replace` option) using URL built from a route defined by name given
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||||
|
|
||||||
|
Note: currently collections in query-parameters are encoded as field-value
|
||||||
|
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||||
|
differently, convert the collections to strings first.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||||
|
([history name]
|
||||||
|
(navigate history name nil))
|
||||||
|
([history name {:keys [path-params query-params replace] :as opts}]
|
||||||
|
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||||
|
path (reitit/match->path match query-params)]
|
||||||
|
(if replace
|
||||||
|
(.replaceState js/window.history nil "" (-href history path))
|
||||||
|
(.pushState js/window.history nil "" (-href history path)))
|
||||||
|
(-on-navigate history path))))
|
||||||
|
|
||||||
|
(defn
|
||||||
|
^{:see-also ["reitit.frontend/set-query-params"]}
|
||||||
|
set-query
|
||||||
|
"Update query parameters for the current route.
|
||||||
|
|
||||||
|
New query params can be given as a map, or a function taking
|
||||||
|
the old params and returning the new modified params.
|
||||||
|
|
||||||
|
Note: The query parameter values aren't coereced, so the
|
||||||
|
update fn will see string values for all query params."
|
||||||
|
([history new-query-or-update-fn]
|
||||||
|
(set-query history new-query-or-update-fn nil))
|
||||||
|
([history new-query-or-update-fn {:keys [replace] :as opts}]
|
||||||
|
(let [current-path (-get-path history)
|
||||||
|
new-path (rf/set-query-params current-path new-query-or-update-fn)]
|
||||||
|
(if replace
|
||||||
|
(.replaceState js/window.history nil "" (-href history new-path))
|
||||||
|
(.pushState js/window.history nil "" (-href history new-path)))
|
||||||
|
(-on-navigate history new-path))))
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,19 @@
|
||||||
[reitit.coercion.malli :as rcm]
|
[reitit.coercion.malli :as rcm]
|
||||||
[reitit.frontend.test-utils :refer [capture-console]]))
|
[reitit.frontend.test-utils :refer [capture-console]]))
|
||||||
|
|
||||||
|
(deftest query-params-test
|
||||||
|
(is (= {:foo "1"}
|
||||||
|
(rf/query-params (.parse goog.Uri "?foo=1"))))
|
||||||
|
|
||||||
|
(is (= {:foo "1" :bar "aaa"}
|
||||||
|
(rf/query-params (.parse goog.Uri "?foo=1&bar=aaa"))))
|
||||||
|
|
||||||
|
(is (= {:foo ""}
|
||||||
|
(rf/query-params (.parse goog.Uri "?foo="))))
|
||||||
|
|
||||||
|
(is (= {:foo ""}
|
||||||
|
(rf/query-params (.parse goog.Uri "?foo")))))
|
||||||
|
|
||||||
(defn m [x]
|
(defn m [x]
|
||||||
(assoc x :data nil :result nil))
|
(assoc x :data nil :result nil))
|
||||||
|
|
||||||
|
|
@ -228,23 +241,30 @@
|
||||||
:expires_in 3600}}})
|
:expires_in 3600}}})
|
||||||
(m (rf/match-by-path router "/5?mode=foo#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600"))))))))
|
(m (rf/match-by-path router "/5?mode=foo#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600"))))))))
|
||||||
|
|
||||||
(deftest update-path-query-params-test
|
(deftest set-query-params-test
|
||||||
(is (= "foo?bar=1"
|
(is (= "foo?bar=1"
|
||||||
(rf/update-path-query-params "foo" assoc :bar 1)))
|
(rf/set-query-params "foo" {:bar 1})
|
||||||
|
(rf/set-query-params "foo" #(assoc % :bar 1))))
|
||||||
|
|
||||||
(testing "Keep fragment"
|
(testing "Keep fragment"
|
||||||
(is (= "foo?bar=1&zzz=2#aaa"
|
(is (= "foo?bar=1&zzz=2#aaa"
|
||||||
(rf/update-path-query-params "foo?bar=1#aaa" assoc :zzz 2))))
|
(rf/set-query-params "foo?bar=1#aaa" #(assoc % :zzz 2)))))
|
||||||
|
|
||||||
(is (= "foo?asd=1&bar=1"
|
(is (= "foo?asd=1&bar=1"
|
||||||
(rf/update-path-query-params "foo?asd=1" assoc :bar 1)))
|
(rf/set-query-params "foo?asd=1" #(assoc % :bar 1))))
|
||||||
|
|
||||||
(is (= "foo?bar=1"
|
(is (= "foo?bar=1"
|
||||||
(rf/update-path-query-params "foo?asd=1&bar=1" dissoc :asd)))
|
(rf/set-query-params "foo?asd=1&bar=1" #(dissoc % :asd))))
|
||||||
|
|
||||||
|
(is (= "foo?bar"
|
||||||
|
(rf/set-query-params "foo?asd=1&bar" #(dissoc % :asd))))
|
||||||
|
|
||||||
|
(is (= "foo?bar"
|
||||||
|
(rf/set-query-params "foo" #(assoc % :bar ""))))
|
||||||
|
|
||||||
(is (= "foo"
|
(is (= "foo"
|
||||||
(rf/update-path-query-params "foo?asd=1" dissoc :asd)))
|
(rf/set-query-params "foo?asd=1" #(dissoc % :asd))))
|
||||||
|
|
||||||
(testing "Need to coerce current values manually"
|
(testing "Need to coerce current values manually"
|
||||||
(is (= "foo?foo=2"
|
(is (= "foo?foo=2"
|
||||||
(rf/update-path-query-params "foo?foo=1" update :foo #(inc (js/parseInt %)))))))
|
(rf/set-query-params "foo?foo=1" (fn [q] (update q :foo #(inc (js/parseInt %)))))))))
|
||||||
|
|
|
||||||
|
|
@ -30,33 +30,53 @@
|
||||||
(is (= "/" url)
|
(is (= "/" url)
|
||||||
"start at root")
|
"start at root")
|
||||||
(rfe/push-state ::foo))
|
(rfe/push-state ::foo))
|
||||||
|
;; 0. /
|
||||||
|
;; 1. /foo
|
||||||
2 (do (is (= "/foo" url)
|
2 (do (is (= "/foo" url)
|
||||||
"push-state")
|
"push-state")
|
||||||
(.back js/window.history))
|
(.back js/window.history))
|
||||||
|
;; 0. /
|
||||||
3 (do (is (= "/" url)
|
3 (do (is (= "/" url)
|
||||||
"go back")
|
"go back")
|
||||||
(rfe/push-state ::bar {:id 1}))
|
(rfe/navigate ::bar {:path-params {:id 1}}))
|
||||||
|
;; 0. /
|
||||||
|
;; 1. /bar/1
|
||||||
4 (do (is (= "/bar/1" url)
|
4 (do (is (= "/bar/1" url)
|
||||||
"push-state 2")
|
"push-state 2")
|
||||||
(rfe/replace-state ::bar {:id 2}))
|
(rfe/replace-state ::bar {:id 2}))
|
||||||
|
;; 0. /
|
||||||
|
;; 1. /bar/2
|
||||||
5 (do (is (= "/bar/2" url)
|
5 (do (is (= "/bar/2" url)
|
||||||
"replace-state")
|
"replace-state")
|
||||||
(.back js/window.history))
|
(rfe/set-query {:a 1}))
|
||||||
6 (do (is (= "/" url)
|
;; 0. /
|
||||||
"go back after replace state")
|
;; 1. /bar/2
|
||||||
|
;; 2. /bar/2?a=1
|
||||||
|
6 (do (is (= "/bar/2?a=1" url)
|
||||||
|
"update-query with map")
|
||||||
|
(rfe/set-query #(assoc % :b "foo") {:replace true}))
|
||||||
|
;; 0. /
|
||||||
|
;; 1. /bar/2
|
||||||
|
;; 2. /bar/2?a=1&b=foo
|
||||||
|
7 (do (is (= "/bar/2?a=1&b=foo" url)
|
||||||
|
"update-query with fn")
|
||||||
|
(.go js/window.history -2))
|
||||||
|
;; 0. /
|
||||||
|
8 (do (is (= "/" url)
|
||||||
|
"go back two events")
|
||||||
|
|
||||||
;; Reset to ensure old event listeners aren't called
|
;; Reset to ensure old event listeners aren't called
|
||||||
(rfe/start! router
|
(rfe/start! router
|
||||||
(fn on-navigate [match history]
|
(fn on-navigate [match history]
|
||||||
(let [url (rfh/-get-path history)]
|
(let [url (rfh/-get-path history)]
|
||||||
(case (swap! n inc)
|
(case (swap! n inc)
|
||||||
7 (do (is (= "/" url)
|
9 (do (is (= "/" url)
|
||||||
"start at root")
|
"start at root")
|
||||||
(rfe/push-state ::foo))
|
(rfe/push-state ::foo))
|
||||||
8 (do (is (= "/foo" url)
|
10 (do (is (= "/foo" url)
|
||||||
"push-state")
|
"push-state")
|
||||||
(rfh/stop! @rfe/history)
|
(rfh/stop! @rfe/history)
|
||||||
(done))
|
(done))
|
||||||
(do
|
(do
|
||||||
(is false (str "extra event 2" {:n @n, :url url}))
|
(is false (str "extra event 2" {:n @n, :url url}))
|
||||||
(done)))))
|
(done)))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue