Reputation: 10207
I just updated my Rails 6 app to Rails 7 and I am finding it difficult to hook my old "Vanilla Javascript" code into Rails' new setup with Turbo, Stimulus, and ImportMaps.
To make things easier, I simple ran rails new NewAppName
on the command line and then migrated my old legacy files bit by bit into the new application.
The ImportMap was generated for me by Rails. I only added the last line to include my old custom Javascript files.
config/importmap.rb:
# Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/components", under: "components" # these are the custom JS files from my old Rails 6 app
app/views/layouts/application.html.erb:
<head>
...
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= yield :head %><!-- load page specific custom JS here -->
...
</head>
This is a typical JS file from my old app.
app/javascript/components/table_row.js:
function TableRow() {
const rows = document.querySelectorAll('table tr[data-url]');
rows.forEach(row => {
row.addEventListener('click', function(e) {
handleClick(row, e);
});
});
function handleClick(row, e) {
const url = row.dataset.url;
window.document.location = url;
}
}
document.addEventListener('turbo:load', TableRow);
I replaced DOMContentLoaded
with turbo:load
in all my JS files to make it work with Turbo (hope that's correct?).
This is how I inject page-specific JS on certain pages:
app/views/quotes/index.html.erb
<% content_for(:head) do %>
<%= javascript_import_module_tag "components/table_row" %>
<% end %>
The problem now is that all this works somehow, however sometimes it doesn't and it's not very reliable. For example, the table_row.js
file should be loaded on index pages but sometimes it's not loaded and I don't really understand why.
I suspect that I mis-configured Turbo or Stimulus or my ImportMap somehow but don't really know where.
Can anybody tell me what I'm missing here?
Upvotes: 3
Views: 2197
Reputation: 2339
I am not using Importmaps
but I rather bundle in the back end with jsbundling
and esbuild
and serve my static assets the good old way with Sprockets
. So I will try to not say something that would not apply to your case .
But the first thing I see is that you were initializing your sprinkles in Rails 6 with DOMContentLoaded
which basically means you were not using Turbolinks
. Then using the newer Turbo
now kinda breaks your JS.
What is Turbo
? Turbo is an attempt to make the loading of your assets more efficient as the head
of your DOM is basically untouched between reloads => Your JS stays the same from page to page. (You can force reload the head though but I won't get into that).
On top of being more efficient it makes your page look more fluid.
If you add stimulus
and leverage the Turbo frames
and Turbo streams
you can have a very efficient Single Page App. Yeah basically Rails 7 is really into SPA territory. (yet with a front-end much simpler than react
or advanced frameworks like angular
etc)
Ok now what I see :
Your sprinkle function TableRow()
is added into the <head></head>
with a content_for
helper.
There are two things here :
TableRow()
in :head
then fine, it will be included in the Javascript of your app. Though it will not be replayed between pages. Just because document.addEventListener('turbo:load', TableRow);
is also included in it, and then will be played only at first load.TableRow()
in :head
, then I am not even sure it will be included in the Javascript if you happen to later visit a page that has the right content_for
helper. Just because the Javascript is not changing between pages.This is probably why you see inconsistent results.
The solution would be to force Turbo to reload the Javascript every time you visit a new page (just like the old Rails way). But to be honest you are kinda killing all the goodness in turbo
in doing that.
Now a solution
If you are using Stimulus then you can fix all that. Stimulus is a fantastic small library that can attach any sprinkle to your DOM easily.
So you can create a Stimulus controller, and add your TableRow()
inside the Stimulus controller connect()
method. The connect method is some Javascript that is played each time the controller is attached to a DOM element (and this DOM elements is appended to the page). So basically all your Javascript is static and not changing from page to page. But when Stimulus see the hook to your specific controller it will trigger the connect()
method and play your TableRow()
that lives inside it.
Basically in the old time the Javascript was played once and for good each time it was loaded on a page. Now it is basically permanent across pages, and some Stimulus hooks play that Javascript when needed.
I won't go into Stimulus
code here as this answer may look pretty bloated, but now you get a picture of Stimulus / turbo.
Upvotes: 6