Implement Bootstrap active tabs with turbo-frames?

Given a page with separate turbo-frames, one for a Bootstrap tab menu and the other for some content:

page.html.erb:

<body>
...
<div class="row">
  <turbo-frame id="nav">
    <%= render 'tabs' %>
  </turbo-frame>
</div>

<div class="row">
  <turbo-frame id="content" src="/content">
  </turbo-frame>
</div>
...
</body>

tabs.html.erb:

<turbo-frame id="holdings-nav">
  <ul class="nav nav-tabs">
  <li class="nav-item">
    <a class="nav-link active" aria-current="page" href="<%= content_1_path %>" data-turbo-frame="content">Content X</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="<%= content_2_path %>" data-turbo-frame="content">Content Y</a>
  </li>
</ul>
</turbo-frame>

This works great; clicking on either link in the tabbed nav bar displays the appropriate content page in the appropriate location via Turbo.

My question is now, what is the recommended way to change active CSS class on the tab bar when clicking the link using Turbo frames? Should I wrap the Turbo frames in page.html.erb in another Turbo frame, then have the nav links target that frame?

Or is it just easier to use a bit of Javascript linked to the onClick handler?

Surely someone has sorted this out? Thanks!!

3 Likes

I had a similar use case. I have a bunch of links. I want to style them differently if they are active. It sounds like a good candidate for what I call an: “active_link” controller:

import {
  Controller
} from "@hotwired/stimulus"

// some links are "current": (i.e. the current page you are on)
// other links are non-current (i.e. point to a page that you are not currently on)

export default class extends Controller {

  static classes = ["current", "default"]
  static targets = ["link"]

  connect() {
    if (this.isApplicable()) {
      this.linkTargets.forEach(link => {
        if (window.location.href === link.href) {
          link.classList.add(...this.currentClasses)
          link.classList.remove(...this.defaultClasses)
        } else {
          link.classList.add(...this.defaultClasses)
          link.classList.remove(...this.currentClasses)
        }
      })
    }
  }

  isApplicable() {
    return this.linkTargets.some(link => {
      return window.location.href == link.href
    })
  }
}

And the view:

      <div data-controler="active-link" 
           data-active-link-current-class="bg-gray-100 text-gray-900"
           data-active-link-default-class="text-gray-700"
      >
        <%= link_to("Reports", reports_path, "data-active-link-target": "link", role:"menuitem", tabindex:"0", class: "text-gray-700 block px-4 py-2 text-sm" ) %>
        <%= link_to("New Report", new_report_path, "data-active-link-target": "link", role:"menuitem", tabindex:"1", class: "text-gray-700 block px-4 py-2 text-sm" ) %>
      </div>
  1. I set the styling class for active links.
  2. I set the styling for the default links.
  3. Then using the the window’s href, we check for whether they match the link, and style it accordingly.

Works for me. But it relies on the browser url to match. Consider whether it will suit your needs if you are using a turbo frame and the URL does not change.

1 Like