Make JavaScript Work Seamlessly with or without Turbo in Ruby on Rails
If you're working on a Ruby on Rails project, you've likely encountered Turbo — a powerful tool from Hotwire that optimizes page loads and updates without requiring a full page refresh. It's fantastic for building fast and interactive web applications, but it can sometimes throw a wrench into your existing JavaScript functions.
Let's dive into a practical example. Imagine you have a simple JavaScript function that removes a CSS class .js-only
from elements once the page is loaded. Here's how you can make sure this function works whether Turbo is present or not.
The Scenario
You have elements on your page with the class .js-only
, and you want these elements to have this class removed as soon as JavaScript is available. Here's our initial function:
const removeJsOnlyClass = () => {
document.querySelectorAll('.js-only').forEach(element => {
element.classList.remove('js-only');
});
};
The Problem
This function works great when the page loads, but with Turbo, the page load event isn't always fired. Instead, Turbo uses its own custom events. This can cause your function not to run when navigating through your application.
The Solution
To make sure your function runs both on a full page load and when Turbo renders a new page, you need to listen to both the standard DOMContentLoaded
event and the Turbo-specific turbo:render
event. Here's how you can do that:
const removeJsOnlyClass = () => {
document.querySelectorAll('.js-only').forEach(element => {
element.classList.remove('js-only');
});
};
document.addEventListener('DOMContentLoaded', removeJsOnlyClass, { once: true });
document.addEventListener('turbo:render', removeJsOnlyClass);
Why This Works
By combining these event listeners, your function is guaranteed to run:
Once when the page initially loads (thanks to
DOMContentLoaded
).Each time Turbo loads new content without a full page refresh (thanks to
turbo:render
).
Creating Your Own Custom Event Listener
To avoid writing both listeners each time you need this functionality, you can create a custom function that handles this for you. This way, you can simply call your custom function and let it take care of the rest.
Here’s how you can create a helper function for this:
const addTurboAndDomListener = (eventName, callback) => {
document.addEventListener('DOMContentLoaded', callback, { once: true });
document.addEventListener('turbo:render', callback);
};
const removeJsOnlyClass = () => {
document.querySelectorAll('.js-only').forEach(element => {
element.classList.remove('js-only');
});
};
addTurboAndDomListener('removeJsOnlyClass', removeJsOnlyClass);
This makes your code cleaner and ensures that any function you want to run on both full page load and Turbo renders can be easily managed.
Conclusion
Integrating Turbo into your Ruby on Rails project doesn't have to complicate your JavaScript. By understanding the events Turbo uses and how to hook into them, you can ensure your JavaScript functions operate smoothly across your application. With just a few lines of code, you can make sure your scripts are always up and running, keeping your app interactive and responsive. And with a handy custom event listener function, you can keep your code DRY (Don't Repeat Yourself) and maintainable.