I'm tryng to open a Bootstrap 4 tab after page load. It does work if I refresh the page, but if I navigate within the site, I get a query selector empty error.
This is my code, that's basically a port I did of this https://webdesign.tutsplus.com/tutorials/how-to-add-deep-linking-to-the-bootstrap-4-tabs-component--cms-31180 because I'm using Bootstrap-native, so I had to rewrite it in vanilla Javascript.
document.addEventListener("turbolinks:load", function() {
let url = location.href.replace(/\/$/, "");
if (location.hash) {
const hash = url.split("#");
document.querySelector('#nav-tab a[href="#'+hash[1]+'"]').Tab.show();
url = location.href.replace(/\/#/, "#");
history.replaceState(null, null, url);
setTimeout(() => {
window.scrollTo(0,0);
}, 400);
}
});
I placed it just before the closing body tag. If I write http://www.myURL#mytab
and click refresh, the page refresh and the tab is changed, but if I get there from a (turbo)link, it does not find the tab to query select. I'm afraid the problem is with the "load" event, I tried different methods but I can't get it to work.
If you are including this code in a <script>
at the end of the <body>
, you probably don't need to listen for the turbolinks:load
event. Why? On the first load, the browser will be able to query any elements positioned before the script element. On Turbolinks loads scripts in the <body>
will have access to all rendered elements on the page.
It's worth adding that by calling document.addEventListener("turbolinks:load", …)
in a body <script>
element, the listener will be called on every subsequent page load, not just on the page where the script is rendered. If the
#nav-tab
elements don't exist on a subsequent page load, then you'll see the querySelector
error. What's more, if you include the script on more than one page, then the listener will be duplicated again and again and again, which is probably not what you want!
So the first step to fix your issue is to remove the event listener. We'll wrap your code in an immediately invoked function to prevent polluting the global scope:
;(function() {
let url = location.href.replace(/\/$/, "");
if (location.hash) {
const hash = url.split("#");
document.querySelector('#nav-tab a[href="#'+hash[1]+'"]').Tab.show();
url = location.href.replace(/\/#/, "#");
history.replaceState(null, null, url);
setTimeout(() => {
window.scrollTo(0,0);
}, 400);
}
})();
The next thing to know is that Turbolinks manages its own cache of visited pages, so that when a user taps "Back", a page is rendered from this cache. To do this, it has a system for adding to the browser's own history
stack. If you bypass the Turbolinks system, and call history.replaceState
(or history.pushState
) yourself, then you could end up breaking the "Back" navigations. Turbolinks doesn't have a documented way to manually add to its history stack, but you could try the following:
;(function() {
let url = location.href.replace(/\/$/, "");
if (location.hash) {
const hash = url.split("#");
document.querySelector('#nav-tab a[href="#'+hash[1]+'"]').Tab.show();
url = location.href.replace(/\/#/, "#");
Turbolinks
.controller
.replaceHistoryWithLocationAndRestorationIdentifier(url, Turbolinks.uuid())
setTimeout(() => {
window.scrollTo(0,0);
}, 400);
}
})();
Note, this is undocumented so may not be publically available in future versions.
Finally, it might be worth considering including this snippet in your main application JavaScript bundle, and load it in the <head>
rather than in the body. In this case you would need to use a `turbolinks:load handler. It might look something like:
document.addEventListener('turbolinks:load', function () {
let url = location.href.replace(/\/$/, "");
const hash = url.split("#");
const navLink = document.querySelector('#nav-tab a[href="#'+hash[1]+'"]')
if (location.hash && navLink) {
navLink.Tab.show();
url = location.href.replace(/\/#/, "#");
Turbolinks
.controller
.replaceHistoryWithLocationAndRestorationIdentifier(url, Turbolinks.uuid())
setTimeout(() => {
window.scrollTo(0,0);
}, 400);
}
});