Skip to content

Commit

Permalink
add popped parameter and liveSocket.updateNavigationUserdata
Browse files Browse the repository at this point in the history
  • Loading branch information
SteffenDE committed Feb 13, 2024
1 parent 03fb8bf commit 4cb9264
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 11 deletions.
14 changes: 9 additions & 5 deletions assets/js/phoenix_live_view/live_socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ export default class LiveSocket {
this.owner(el, view => JS.exec(eventType, encodedJS, view, el))
}

updateNavigationUserdata(callback){
Browser.updateCurrentState(state => Object.assign(state, {userData: callback(state.userData)}))
}

// private

execJSHookPush(el, phxEvent, data, callback){
Expand Down Expand Up @@ -704,7 +708,7 @@ export default class LiveSocket {
const previousLocation = clone(this.currentLocation)
// early return if the navigation does not actually navigate anywhere (hashchange)
if(!this.registerNewLocation(window.location)){ return }
this.withNavigationGuard(window.location.href, previousLocation.href, () => {
this.withNavigationGuard(window.location.href, previousLocation.href, true, () => {
let {type, id, root, scroll, userData} = event.state || {}
let href = window.location.href

Expand Down Expand Up @@ -785,7 +789,7 @@ export default class LiveSocket {
}

pushHistoryPatch(href, linkState, targetEl){
this.withNavigationGuard(href, this.currentLocation.href, (userData) => {
this.withNavigationGuard(href, this.currentLocation.href, false, (userData) => {
if(!this.isConnected() || !this.main.isMain()){ return Browser.redirect(href) }
if(userData) Browser.updateCurrentState(state => Object.assign(state, {userData}))

Expand All @@ -809,7 +813,7 @@ export default class LiveSocket {
}

historyRedirect(href, linkState, flash){
this.withNavigationGuard(href, this.currentLocation.href, (userData) => {
this.withNavigationGuard(href, this.currentLocation.href, false, (userData) => {
if(!this.isConnected() || !this.main.isMain()){ return Browser.redirect(href, flash) }
if(userData) Browser.updateCurrentState(state => Object.assign(state, {userData}))

Expand Down Expand Up @@ -860,11 +864,11 @@ export default class LiveSocket {
// Therefore, we push the previous location again, see bindNav for details.
//
// When the callback is done, it must call `afterNavigation` with the same arguments (to, from).
withNavigationGuard(to, from, callback, cancel = function(){}){
withNavigationGuard(to, from, popped, callback, cancel = function(){}){
// the beforeEach navigation guard can return a promise that must resolve
// to false in order to cancel the navigation
// every other value proceeds with the navigation
const guardResult = this.navigationCallbacks["beforeEach"](to, from)
const guardResult = this.navigationCallbacks["beforeEach"](to, from, popped)
Promise.resolve(guardResult).then(result => {
if(result === false){
cancel()
Expand Down
33 changes: 27 additions & 6 deletions guides/client/js-interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,16 @@ let liveSocket = new LiveSocket("/live", Socket, {
// other options left out
// ...
navigation: {
beforeEach() {
let scrollPositions = {}
Array.from(document.querySelectorAll("[data-restore-scroll]")).forEach(el => {
scrollPositions[el.id] = el.scrollTop
})
return { scrollPositions }
beforeEach(_to, _from, popped) {
// in case of a popstate event, the current state cannot be saved
// because the navigation has already happened
if (!popped) {
let scrollPositions = {}
Array.from(document.querySelectorAll("[data-restore-scroll]")).forEach(el => {
scrollPositions[el.id] = el.scrollTop
})
return { scrollPositions }
}
},
afterEach(_to, _from, userData) {
// restore scroll positions
Expand All @@ -392,7 +396,24 @@ let liveSocket = new LiveSocket("/live", Socket, {
}
}
})

let scrollTimer
window.addEventListener("scroll", _e => {
clearTimeout(scrollTimer)
scrollTimer = setTimeout(() => {
let scrollPositions = {}
Array.from(document.querySelectorAll("[data-restore-scroll]")).forEach(el => {
scrollPositions[el.id] = el.scrollTop
})
liveSocket.updateNavigationUserdata(data => Object.assign(data, {scrollPositions}))
}, 100)
})
```

This assumes that all scrollable containers have a `data-restore-scroll` attribute,
as well as a unique `id`.

Note that due to limitations of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API),
the data returned by the `beforeEach` callback is not saved when the navigation is triggered by navigating back
or forward in the history. Therefore, the scroll state needs to be continuously saved while being on the
current page. This is done in a `scroll` event listener above, which saves the scroll state every 100ms.

0 comments on commit 4cb9264

Please sign in to comment.