I have a Rails app with a fair amount of JS, primarily to make the app more responsive (e.g. save changes without repainting the whole page, etc.). I place the controler name and action as classes on the “body” element so that I can scope code to a single page). In general, my single-controller JS assets look like this:
$('document').ready(() => {
if ($('body.books').length > 0) {
// One section of code representing related functionality
$(...).click(ev => { ... code ... };
$(...).change(ev => { some-function-name(ev) });
function some-function-name(ev) {
... code ...
};
// Another section of related code
...
}
});
Now to my problem. I happen to have very similar code in two different pages. So similar, that some of the function names are the same. It seems like this is becoming a problem, at least because every JS module gets loaded into every page, and I’m concerned that these functions are overwriting each other. Is this an issue I should be worried about? If so, what do you do to avoid this issue?
As an observation - I’ve written code similar this in the past but I think there’s much better alternatives these days. You might want to look at stimulus to see what a more modern approach could look like. With stimulus, you define code that you attach to the html elements on the page and you don’t have to worry about the scoping issues that you have here. If you’re new to some of the concepts there it might be a bit confusing, but it’s worth learning about in my opinion.
If you really want to stick it out with this pattern, you could extract your page-specific code into separate files/functions and just call those to make sure that functions are defined within closures, which will ensure nothing wonky happens:
function booksPage() {
// One section of code representing related functionality
$(...).click(ev => { ... code ... };
$(...).change(ev => { some-function-name(ev) });
function some-function-name(ev) {
... code ...
};
// Another section of related code
...
}
}
$(document).ready(() => {
if ($('body').hasClass('books')) {
booksPage();
} else if ($('body').hasClass('authors')) {
authorsPage();
}
});
I’m sure there’s other options out there for this too.
Thanks Mark. Your approach definitely makes sense, and I will give it serious consideration My sense is that, with the way Javascript is defined, there are any number of solutions, each with their own tradeoffs. What I’m curious about is if there is a “Rails way”, that is a generally-accepted preferred approach for use with Rails projects. I can imagine, with Rails being as opinionated as it is, that there would be one.
Gotcha - I think a more idiomatic approach for sticking with the page-specific js is to lean on the content_for helper and yielding a page_js block from your layout file like:
# books/index.html.erb
<% content_for(:page_js) do %>
<script>
$(document).ready(...)
</script>
<% end %>
The one downside to this, is that you miss out on the benefits of the asset pipeline since your scripts are inline on the page… But you could define your page specific code in sprockets/webpack and invoke it from the content_for block:
# books/index.html.erb
<% content_for(:page_js) do %>
<script>
$(document).ready(bookPageJs) // where bookPageJs is a function defined in app/assets/javascripts/pages/bookpage.js and included via sprockets application.js bundle.
</script>
<% end %>
Maybe that’s more inline with what you were thinking? I know what you mean about there seeming like there should be a “Rails Way” to do this - the interplay here has always been awkward from what I’ve seen until I came across stimulus honestly.