I have a sidebar with some links that, when clicked, alter a different turbo frame, basically an SPA. To highlight the current page, I use a Stimulus controller that checks which URL is currently in window.location, and add the appropriate CSS to the right nav item:
_nav.html.erb
several links like this:
...
<%= spa_link_to podcasts_path, data: { navid: "podcasts" }, class: 'body_text_alternate' do %>
<%= inline_svg 'icons/search.svg', class: 'icon icon_small', aria_hidden: 'true' %>
Browse
<% end %>
helper
def spa_link_to(name = nil, options = nil, &block)
default_data_attributes = { controller: "nav", action: "nav#navigate", turbo_frame: "main-app-frame" }
options[:data] ||= {}
options[:data].reverse_merge!(default_data_attributes)
link_to(name, options, &block)
end
nav_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
updateActiveLink() {
// Deactivate all links
document.querySelectorAll(`a[data-navid]`).forEach (link => {
link.parentElement.classList.remove('active');
});
// Get the current URL
const currentPath = window.location.pathname;
const currentActiveNavid = currentPath == "/" ? "podcasts" : currentPath.split("/")[1]
// Activate current open link
const navEl = document.querySelector(`a[data-navid="${currentActiveNavid}"]`)
navEl.parentElement.classList.add('active');
}
// Listen for click event on navigation links
navigate(event) {
// Get the href attribute of the clicked link
const url = event.currentTarget.getAttribute('href');
// Update the browser's URL
history.pushState({}, '', url);
Turbo.navigator.history.push(url);
this.updateActiveLink();
}
}
Then for example I’ll have app/views/podcasts/index.html.erb with:
<%= turbo_frame_tag "main-app-frame" do %>
<main class="template_page">
<header class="header_page">
...
So clicking on the sidebar link will navigate to the Podcasts index page, replacing the content of “main-app-frame” with podcasts/index.
This setup works fine and as you can see in the Stimulus controller, the browser URL is updated as well.
However, Browser back/forward buttons and the browser history behave oddly. Two entries for the same page are added (maybe because I’m calling history.pushState and Turbo.navigator.history.push? But I read you are supposed to call both), and navigating back only works after you pressed the Back button twice (probably same underlying reason).
I’ve tried all sorts of combinations of having both Turbo.navigator.history.push and history.pushState, either one, etc, nothing works and I’m pulling my hair out. Any ideas?