Skip to content

Commit

Permalink
Add navigation guards to liveSocket
Browse files Browse the repository at this point in the history
This PR introduces a new option that can be specified when creating a
livesocket instance: `navigation`.

Navigation is an object that can define two functions: `beforeEach` and
`afterEach`. The `beforeEach` function is called before each navigation
event and can for example be used to store custom scroll positions. If
the `beforeEach` function returns `false`, the navigation will be aborted.
This can be used to implement a warning when the user tries to navigate
while a form is still being edited. The `afterEach` function is called
after a navigation event has been completed. It can be used to restore
the scroll position that was stored in the `beforeEach` function.
  • Loading branch information
SteffenDE committed Jan 24, 2024
1 parent 051fa28 commit 8e7d50b
Showing 1 changed file with 16 additions and 0 deletions.
16 changes: 16 additions & 0 deletions assets/js/phoenix_live_view/live_socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export default class LiveSocket {
this.sessionStorage = opts.sessionStorage || window.sessionStorage
this.boundTopLevelEvents = false
this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})
this.navigationCallbacks = Object.assign({beforeEach: closure(true), afterEach: closure()}, opts.navigation || {})
this.transitions = new TransitionSet()
window.addEventListener("pagehide", _e => {
this.unloaded = true
Expand Down Expand Up @@ -687,6 +688,13 @@ export default class LiveSocket {
}, 100)
})
window.addEventListener("popstate", event => {
if(this.navigationCallbacks["beforeEach"](window.location.href) === false){
// we want to stay at the current location; push the current location back
// and then the old location where we're coming from
Browser.pushState("push", history.state || {}, window.location.href)
Browser.pushState("push", history.state || {}, this.currentLocation.href)
return
}
if(!this.registerNewLocation(window.location)){ return }
let {type, id, root, scroll} = event.state || {}
let href = window.location.href
Expand All @@ -695,11 +703,13 @@ export default class LiveSocket {
this.requestDOMUpdate(() => {
if(this.main.isConnected() && (type === "patch" && id === this.main.id)){
this.main.pushLinkPatch(href, null, () => {
this.navigationCallbacks["afterEach"](href)
this.maybeScroll(scroll)
})
} else {
this.replaceMain(href, null, () => {
if(root){ this.replaceRootHistory() }
this.navigationCallbacks["afterEach"](href)
this.maybeScroll(scroll)
})
}
Expand Down Expand Up @@ -757,6 +767,8 @@ export default class LiveSocket {
}

pushHistoryPatch(href, linkState, targetEl){
if(this.navigationCallbacks["beforeEach"](href) === false) return

if(!this.isConnected() || !this.main.isMain()){ return Browser.redirect(href) }

this.withPageLoading({to: href, kind: "patch"}, done => {
Expand All @@ -773,9 +785,12 @@ export default class LiveSocket {
Browser.pushState(linkState, {type: "patch", id: this.main.id}, href)
DOM.dispatchEvent(window, "phx:navigate", {detail: {patch: true, href, pop: false}})
this.registerNewLocation(window.location)
this.navigationCallbacks["afterEach"](window.location.href)
}

historyRedirect(href, linkState, flash){
if(this.navigationCallbacks["beforeEach"](href) === false) return

if(!this.isConnected() || !this.main.isMain()){ return Browser.redirect(href, flash) }

// convert to full href if only path prefix
Expand All @@ -790,6 +805,7 @@ export default class LiveSocket {
Browser.pushState(linkState, {type: "redirect", id: this.main.id, scroll: scroll}, href)
DOM.dispatchEvent(window, "phx:navigate", {detail: {href, patch: false, pop: false}})
this.registerNewLocation(window.location)
this.navigationCallbacks["afterEach"](window.location.href)
}
done()
})
Expand Down

0 comments on commit 8e7d50b

Please sign in to comment.