Search code examples
htmlcssruby-on-railssimple-formturbolinks

Trouble with turbolinks in Rails: collapsible menu and sending checkbox form


I'm fairly new to developing in Rails (developing in general to be honest), so I hope somebody can give a clue on how to solve this...

I'm creating a rails web-app (ruby 2.6.5, rails 6.0.1) using the Inspinia template from WrapBootstrap.

The template has a navigation side-bar with pre-configured behavior (drop-down/collapsible second level menu items and such). This was working fine.

However I need to create a view page to control commissions, so to mark the paid commissions I created a simple_form form with a single checkbox that will toggle the 'paid' vs. 'not-paid' attribute.

What happens is that when I click the checkbox, I get an error that the turbolinks was not found, even though I have the turbolinks gem.

So I included //= require turbolinks in application.js

But when I do this, the collapsible menu items with sub-levels in the navigation side-bar just stay open and don't collapse anymore.

I'm not sure what to do, because I need to require turbolinks for my form to work properly, but I also want the side-bar menus to collapse.

screenshot of the issue

Example of HTML menu item (when inspecting the HTML in the browser, with the side-bar working properly, there is class called 'aria-expanded' that doesn't show on my normal HTML):

<li class="border-top <%= 'active' if current_page?('/operations') || current_page?('/operations/new') || current_page?('/vencimento') %>">
   <a href="#"><i class="fa fa-cogs"></i><span class="nav-label" data-i18n="nav.menulevels"> Operations </span><span class="fa arrow"></span></a>
      <ul class="nav nav-second-level">
         <li> <%= link_to "Operations List", operations_path %> </li>
         <li> <%= link_to "New Operation", new_operation_path %> </li>
         <li> <%= link_to "Due Options", vencimento_path %> </li>
      </ul>
 </li>

Code for the form (the additional field is just to make this unique to avoid confusion with other updates):

<%= simple_form_for(operation, remote: true) do |f|%>
   <%= f.check_box :status, label: false, onchange: 'Rails.fire(this.form, "submit")', id: "checkbox-#{operation.id}", value: true, input_html: {  checked_value: true, unchecked_value: false }, checked: operation.status == 'Terminada' ? 'checked' : '' %>
   <%= f.simple_fields_for :toggle_com do |d| %>
      <%= d.input :commission, as: :hidden, label: false, input_html: { value: 'toggle' } %>
   <% end %>
<% end %>

Code in application.js:

//= require rails-ujs
//= require activestorage
//= require jquery/jquery-3.1.1.min.js
//= require popper
//= require bootstrap
//= require pace/pace.min.js
//= require slimscroll/jquery.slimscroll.min.js
//= require metisMenu/jquery.metisMenu.js
//= require inspinia.js
//= require dataTables/datatables.min.js
//= require dataTables/dataTables.bootstrap4.min.js
//= require datapicker/bootstrap-datepicker.js
//= require chartkick
//= require Chart.bundle  

CSS:

.nav > li > a {
  color: $nav-text-color;
  font-weight: 600;
  padding: 14px 20px 14px 25px;
  display: block;
}

.nav.metismenu > li {
  display: block;
  width: 100%;
  position: relative;
}

.nav.metismenu .dropdown-menu > li > a {
  padding: 3px 20px;
  display: block;
}

.nav.navbar-right > li > a {
  color: #999c9e;
}

.nav > li.active > a {
  color: #ffffff;
}

.navbar-default .nav > li > a:hover, .navbar-default .nav > li > a:focus {
  background-color: darken($nav-bg, 3%);
  color: white;
}

.nav .open > a, .nav .open > a:hover, .nav .open > a:focus {
  background: #fff;
}

.nav.navbar-top-links > li > a:hover, .nav.navbar-top-links > li > a:focus {
  background-color: transparent;
}

.nav > li > a i {
  margin-right: 6px;
}
ul.nav-second-level {
  background: darken($nav-bg, 3%);
}

.nav > li.active {
  border-left: 4px solid darken($navy, 2%);
  background: darken($nav-bg, 3%);
}

.nav.nav-second-level > li.active {
  border: none;
}

.nav.nav-second-level.collapse[style] {
  height: auto !important;
}

Solution

  • Using Turbolinks with pre-existing javascript is not that straight forward and you have to wrap you mind around the fact, that there are no hard reloads anymore that would reinitialize your menu or any other javascript on load.

    Your menu stays open, because its state got never reset when navigating away from the page.

    With a hard page reload you get this for free, in turbolinks you have to take care of the teardown in the before-cache event. (https://github.com/turbolinks/turbolinks#preparing-the-page-to-be-cached)

    document.addEventListener("turbolinks:before-cache", function() {
      // reset the menu
      // if your menu uses Bootstrap Collapse then use something like this:
      $('.collapse').collapse('dispose')
      // @see: https://getbootstrap.com/docs/4.5/components/collapse/#collapsedispose
    })
    

    This SO answer https://stackoverflow.com/a/44057187/814031 will give you good practical advice how to approach more complex script setups.

    Here are two other links that maybe help you understand turbolinks:

    https://sevos.io/2017/02/27/turbolinks-lifecycle-explained.html http://www.modernmpa.com/turbolinks

    TL;DR

    And if only your form depends on turbolinks, make that work without it and not use turbolinks at all. That would be my advise.