Rails active link styling with turbo frames

1.2k views Asked by At

I have links made like this:

- @glossaries.each do |g|
  %a.{ :href => glossary_path(g), data: { 'turbo_frame': :'entry' } }
    = g.title

How can I style active link (active glossary) if result of clicking g.title link is rendered in another frame? enter image description here

I've tried using helper to give a link some class if current path equal requested path, but it is not working. No 'active' class is given to link:

def current_class?(test_path)
    return 'active' if request.path == test_path
    ''
  end

When working with frames current path not showing in browser address bar, so I've tried to force it by data-turbo-action" => "advance". With that browser have current path in address bar, but still link do not have 'active' class..

4

There are 4 answers

1
yungindigo On

The reason why it would not update is because the other frame is navigating while the rest of the page stays the same. To make it show the active navigation link you are going to want to either reload both or use some custom JavaScript. I would rather just reload both sides I would do another turbo-frame around the navigation and the sidepanel like so

<%= turbo_frame_tag "glosarries_index" do %>
  ... Navigation links would be here but remove the data-turbo-frame it will automatically use the closest parent.
  <%= turbo_frame_tag "entry" do %>
    ...
  <% end %>
<% end %>
0
Xiaohui Zhang On

I write this stimulus controller to handle turbo:click event and make a active state to the clicked element. There is an issue when another turbo:click event handler cancelled the visit, but in this place I don't care about it.

import { Controller } from "@hotwired/stimulus";

export default class extends Controller{
  static targets = ['link']
  static classes = ['active']

  initialize(){
    this.handleTurboClick = this.handleTurboClick.bind(this)
  }

  connect(){
    this.element.addEventListener('turbo:click', this.handleTurboClick)
  }

  disconnect(){
    this.element.removeEventListener('turbo:click', this.handleTurboClick)
  }

  handleTurboClick(event){
    this.linkTargets.forEach(target => {
      if(target == event.target){
        target.classList.add(...this._activeClasses)
      }else{
        target.classList.remove(...this._activeClasses)
      }
    })
  }

  get _activeClasses(){
    return this.activeClasses.length == 0 ? ['active'] : this.activeClasses
  }
}
<div data-controller="turbo-active-link" data-turbo-active-link-active-class="bg-gray-300 hover:bg-gray-300" data-turbo-frame="main">
  <a href="/a" data-turbo-active-link-target="link">link A</a>
  <a href="/b" data-turbo-active-link-target="link">link B</a>
</div
0
TERMINATOR On

I found the above answer worked but was overly complex so I built a more simple solution that requires less boiler plate. This solution also adheres to stimulus convention better as it does not use custom event handlers and only uses targets

The stimulus controller:

import { Controller } from "stimulus";

export default class extends Controller {
  static classes = ['active']

  connect() {
    this.active_link = null;
  }

  disconnect() {
    this.active_link = null;
  }

  handleTurboClick(event) {
    if (this.active_link) {
      this.active_link.classList.remove(this.activeClass);
    }
    this.active_link = event.target.closest('a');
    this.active_link.classList.add(this.activeClass);
  }
}

The html:

<div data-controller="turbo-active-link" data-turbo-active-link-active-class="bg-gray-300 hover:bg-gray-300">
  <a href="/a" action="turbo:click->turbo-active-link#handleTurboClick" data-turbo-frame="main">link A</a>
  <a href="/b" action="turbo:click->turbo-active-link#handleTurboClick" data-turbo-frame="main">link B</a>
</div
0
Alexander V On

I'll post another way of doing this. That's how I style tabs now.

active_tab_controller.js:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["link"]

  connect() {
    this.linkTargets[0].classList.add("active");
    this.linkTargets[1].classList.add("shadow-inner");
  }

  setActive(event) {
    this.linkTargets.forEach(link => {
      link.classList.remove("active")
      link.classList.add("shadow-inner")
    })

    event.currentTarget.classList.add("active")
    event.currentTarget.classList.remove("shadow-inner ")
  }
}

application.tailwind.css:

.active { @apply
  border border-b-0 border-gray-300 bg-gradient-to-b from-neutral-50 to-neutral-100
}

html:

.flex{ data: { controller: "active-tab" } }
  = link_to main_tab_user_project_path(@user, @project), data: {turbo_frame: "tab_content", action: "click->active-tab#setActive", "active-tab-target" => "link"} do
    = image_tag "list.svg"
    .px-1 to-dos
  = link_to second_tab_user_project_path(@user, @project), data: {turbo_frame: "tab_content", action: "click->active-tab#setActive", "active-tab-target" => "link"} do
    = image_tag "link.svg"
    .px-1 links

= turbo_frame_tag "tab_content" do
  = render "main_tab"