Update elements outside of main / restore scroll position on popstate
blimpmason opened this issue · comments
Hey @estrattonbailey — I'm new to your open source toolset and found you via https://github.com/the-couch/slater/. I really appreciate all the work that went into these goodies! I just have a few questions about operator specifically:
- Is there any way to configure it to update body classes, navigation classes, or perhaps anything with a specific data attribute on it that's outside of the
main
element? - Is there any way to include scroll position restoration on popstate, for example when navigating back to a long collection page from the product page using the browser UI? I consider that pretty crucial for UX as it can be super annoying to find where you were and continue browsing if you're plopped back at the top of the page.
Let me know if you have any thoughts!
@blimpmason hey
These are great questions! I've had to deal with both in the past as well.
One
There are a few approaches you could take. Out of the box, you could use operator's events, probably after
, and its state
to apply body classes.
I love the data-attribute idea, that's how I usually approach it. I made this to help with those situations. Use in tandem with operator, they make for a pretty powerful SPA system.
Two
So operator disables this. I realize it's native functionality, but believe it or not, the majority of my clients have asked me to disable it in the past. It used to be in operator, years ago, but I extracted it into this package.
I don't think that it would be possible to use that in tandem with operator at the moment, but I could be wrong. The issue would be that operator doesn't emit a popstate
event, which is the only time you'd want to call scroll.restore()
.
Perhaps we should add a popstate
event
Thoughts?
Hey @estrattonbailey thanks for the quick response!
One
There are a few approaches you could take. Out of the box, you could use operator's events, probably after, and its state to apply body classes.
I took a quick look at that and it looks like state doesn't return the full HTML of the page you're navigating to, so I'm not clear on how I could pull the body classes and inject them into the page before rendering. This would work great for updating a nav as you can simply check the link path against the state location to match the active link element — but there too it could be easier to just update the classes set by liquid logic from the destination page. The PJAX library I've used most often in the past is barba.js and since it returns the full raw HTML it's pretty easy to parse to update classes or even fully replace components outside the main wrapper.
I love the data-attribute idea, that's how I usually approach it. I made this to help with those situations. Use in tandem with operator, they make for a pretty powerful SPA system.
I've been exploring this tool as well as I convert a Shopify theme over to Slater. I'm not super clear on how to take full advantage but I haven't really spent the time to dig too deep yet. Basically I have a bunch of CommonJS modules that will need to be reinitialized after page transitions, and I'm hoping that library can help with that as well via mount/unmount without rewriting too much code. Many of those modules also have simple checks that look for elements in the DOM and only continue if they exist, otherwise return false. Could picoapp help with that logic as well?
Perhaps we should add a popstate event
🤔 I think if an added event means a user could integrate a add-on library like scroll-restoration, that would be a good solve, rather than creating a new option to enable/disable this native scrolling. That way, users might also find another use for the new event that I can't think of right now.
I love the idea of adding both a popstate event and perhaps options for scroll restoration or transition disabling on popstate — moox/pjax library includes that as an option, and swup/swup library simply disables the transition on popstate (I believe). The option to disable transitions on popstate has an added advantage in Safari and any browsers that include their own built in popstate animations. It's a pretty glitchy experience when you swipe the page back for example, the olds page slides off screen, and then the old page fades out and the new page fades in.
In any case I'm thinking that swup makes more sense if I can integrate it easily into Slater, which looks like it could be as easy as calling app.unmount() and app.mount() after page transitions.
@blimpmason thanks for the clarification!
Re: classes, totally understand where you're coming from now, that's a pretty common pattern in Shopify sites. As you've discovered, there's not way to parse the full DOM of the fetched page at the moment. Not sure what that would look like given the current API, but I'm open to suggestions! I'd say the easiest way to do it currently is to wrap each of your templates with an element that has your class attached (or as an attribute), parse that on the after
event, and insert it into the body.
You could also use something like dcx and just manually set them instead of using the liquid tag. Something along these lines:
import dcx from 'dcx'
const cx = dcx(document.body)
router.on('after', state => {
cx({
'template-index': /^\/$/.test(state.location),
'template-page': /^\/page/.test(state.location)
})
})
Could picoapp help with that logic as well?
You betcha. At a base level, that's all it does. It also just adds a thin layer on top that allows for pub/sub events and state to be shared amongst registered components.
The option to disable transitions on popstate...
That's a great idea really, I can explore that!
But yeah swup looks very cool! Had never seen this one before. I'm familiar with pjax and barba.js. And yeah, picoapp should integrate in basically the same way. As another option, I just found this library though the swup repo. Also never heard of that one. There's also this one and another that I can't seem to find right now, but will post if I do.
Hi Eric and David - not sure if there's any more interest in this, but I could also see a need for setting body class if using operator with WordPress. What if we just add state.document = doc
to done()
so middleware and events could access anything from the full page response?
router.on('after', state => {
window.history.pushState({}, '', state.location)
document.body.className = state.document.body.className
document.title = state.title
})