This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-10
Channels
- # announcements (3)
- # asami (19)
- # babashka (38)
- # beginners (42)
- # cider (19)
- # clojure (17)
- # clojure-europe (34)
- # clojure-hungary (3)
- # clojure-nl (1)
- # clojure-norway (53)
- # clojure-uk (7)
- # clojuredesign-podcast (34)
- # conjure (2)
- # cursive (7)
- # data-science (13)
- # datalevin (3)
- # datomic (19)
- # dev-tooling (1)
- # events (1)
- # honeysql (2)
- # hyperfiddle (31)
- # integrant (16)
- # juxt (39)
- # missionary (14)
- # nrepl (14)
- # off-topic (57)
- # overtone (22)
- # podcasts-discuss (1)
- # practicalli (32)
- # reitit (12)
- # releases (2)
- # ring (13)
- # ring-swagger (2)
- # sql (85)
- # squint (75)
Hi, I am faced with the same needs as mentioned in this issue: https://github.com/ingesolvoll/kee-frame/issues/78 To be able to prevent navigation under certain conditions like when form contains unsaved changes on the front end. Since kee-frame now use reitit-frontend under the hood, is there any Reitit solutions to approach this problem?
Route or navigation guards?
Hi 👋
afaik, there is nothing built-in.
I moved our re-frame
app to reitit
. The code guarding against losing changes when navigating around or completely away from the app (other page or closing the page) is rather gnarly.
I'm happy to give you some pointers, but I'm unsure how helpful that would be.
Are you only interested in built-in helpers from reitit
or also in a general approach to solving this?
Hi 👋 Thank you for your feedback. For now, I've put this aside in order to finish the sprint but I plan to dig deeper and study the Reitit and Kee-frame code. Our project uses the latter and it uses Reitit under the hood.
> I'm happy to give you some pointers, but I'm unsure how helpful that would be. Any pointers will be welcome so both 🙂 Thanks.
I built the initial simple implementation by combining the re-frame and prompt examples in the reitit repository.
1. Basic thing to do: on-navigate
checks if it should show a prompt -> very simple case
- https://github.com/metosin/reitit/blob/master/examples/frontend-prompt/src/frontend/core.cljs#L25-L33
2. Our own implementation works differenty
- on-navigate
checks for a ::protected
field in the db
- if it’s nil/false, do the navigation
- else: Show a guard modal with cancel/confirm buttons. Content is generic plus whatever was stored in ::protected
3. It gets tricky when handling the browser’s back button. You want to open the guard modal when user clicks the the back button. You also want to disable back button when the guard modal is already opened. But it’s not possible to disable the back button or override it’s behaviour. A work around is explained here: you can push the current route on the history stack so navigating back leaves the user on the current page.
- https://stackoverflow.com/a/64572567
For this we have a stop-browser-back
and a start-browser-back
function. They get called when we update the ::protected
field in the db. Here they are:
4. Next case to keep in mind: When the back button leads away from your page/app or the user wants to close the tab/window. There you have to add/remove an event listener to the beforeunload
event. This will open a browser alert like window that you can’t configure in any way.
- https://stackoverflow.com/a/7317311
And all this has to work with the small, some time app specific, edge cases, for example:
- Updating URL search params shouldn’t trigger anything: shouldn’t trigger
- Some navigations don’t lose form state but change view: shouldn’t trigger
- Some of our links are explicitly not handled by reitit but should be guarded:
on-navigate
isn’t called, so we have to open the modal AFTER the navigation and somehow revert the navigation if user doesn’t confirm
Hope that gives you some useful starting points. Feel free to ask any questions.
Implementation for 4, ::on-unload-protection
is dispatched on every form change, the protected?
param could also be called edited?
(defn- before-unload-handler [event]
(set! (.-returnValue event) "")
(.preventDefault event))
(defn- clear-unload-protection []
(.removeEventListener js/window "beforeunload" before-unload-handler))
(defn- set-unload-protection []
(.addEventListener js/window "beforeunload" before-unload-handler))
(reg-fx
::on-unload-protection
(fn [protected?]
(if protected?
(set-unload-protection)
(clear-unload-protection))))
And then code for making 3. work:
(def ^:const double-state-marker "double")
(defn- stop-browser-back
"It's not possible to disable navigate back in modern browsers.
Workaround:
1. Push current route on history stack.
2. Navigating back then goes to same page, triggers 'popstate' event, which pushes current route again."
[f]
(.pushState js/window.history double-state-marker "" (.-href js/window.location))
(set!
js/window.onpopstate
(fn []
(.pushState js/window.history double-state-marker "" (.-href js/window.location))
(when (fn? f) (f)))))
(defn- start-browser-back
[f nav-back?]
(set! js/window.onpopstate nil)
(cond
;; if modal was opened by navigating back, go back two entries in history stack
nav-back? (.go js/window.history -2)
(fn? f) (f)
:else (when (= double-state-marker (.-state js/window.history))
(.back js/window.history))))
Obviously, getting 3 or 4 wrong can lead to very unhappy users 😄
Take your time, no need to hurry along other tasks and also no need to provide feedback or questions (but always welcome 🙂 )