0

I currently have the following script in two of my form views.

<script>

$('#standard_calendar').calendar();
$('#time_calendar').calendar({type: 'time'});

</script>

Where I can place them so they're accessible but not mixed in with my HTML?

2 Answers 2

1

Your scripts should ideally be prepackaged and concatenated into a single file to avoid multiple HTTP requests. Instead of relying on loading a particular set of JavaScript on a specific page you can just add a data attribute or class to the body element and use it to toggle your behaviors. This is not only better for performance but will avoid the issues tied to turbolinks and having a persistent browser session across page visits.

I wrote a gem for this a while back that has not been maintained in quite some time but I solved it by basically doing:

<body data-action="<%= action_name %>" data-controller="<%= controller_name %>">

$(document).on('turbolinks:load', function(){
  let data = $('body').data();
  // pagescript: is just the namespace for the events to avoid potential name collisions 
  // Use whatever you want instead
  $(document).trigger('pagescript:' + data.controller + '#' + data.action, data)
             .trigger('pagescript:' + data.controller + '#*', data)
             .trigger('pagescript:' + '*#' + data.action , data);
});

This triggers a set of custom events on every page change. You can add event listeners to listen for specific pages or actions:

// A specific controller_name and action
$(document).on('pagescript:controller_name#action_name', (e) => { });
// Any action belonging to a specific controller
$(document).on('pagescript:controller_name#*', (e) => { });
// Any action matching action_name
$(document).on('pagescript:*#action_name', (e) => { });

For example:

$(document).on('pagescript:users#new', (e) => { 
  alert("Welcome new user!");
});

But the problem can also be solved by better code design. Think in terms of modules that can be used to enhance the behavior in any page instead of writing code that enhances that specific doodad on the specific page.

Sign up to request clarification or add additional context in comments.

Comments

0

You have two options (I assume you have webpacker installed):

  1. You can create a new js file in a app/javascript/components folder. And then import this file into the application.js. application.js is the entry point for all javascript - through <%= javascript_pack_tag 'application' %> in application.html.erb. So then your script will be called on all pages.
  2. Or but this will only work if you don't use turbolinks, you can create a new file for your script in app/javascript/packs and then in the respective view, you call your script with <%= javascript_pack_tag 'file_name' %>. So if you call your file calendar.js it would be <%= javascript_pack_tag 'calendar' %> Then your script will only be called on this one view page.

One more hint if you follow the second option and if you have the main <%= javascript_pack_tag 'application' %> in application.html.erb at the bottom:

This would mean, that you call your calendar script before you render the main application.js which could lead to bugs. A way to avoid that is to create a second yield in application.html.erb and use it like so:

application.html.erb
<body>
    <%= yield %>
    <%= render "shared/footer" %>
    <%= javascript_include_tag 'application' %>
    <%= javascript_pack_tag 'application' %>

    <%= yield(:after_js) %>
  </body>

And then in your view you can do the following:

views/somthing/index.html.erb
...
<%= content_for :after_js do %>
  <%= javascript_pack_tag 'calendar' %>
<% end %>

This flow would be the following: application.html.erb is rendering the body and including the html code from the view files. Then it renders a footer (for example). It then calls application.js which might import some general JS code and packages. And then the second yield calls the block in the view again which has the specific script for the calendar.

5 Comments

The problem with this approach is turbolinks. With turbolinks you have a persistent browser session across multiple pages and once you load a script onto the page it does not just go away when the user clicks on another page.
And this is a problem in terms of performance or what is the problem? Would be happy if you could explain
There is slight performance hit to having another HTTP request but the biggest issue is that turbolinks will cache the javascript so if you want the script to run every time the user visits that page it won't work, it will only fire on the initial page view. See github.com/turbolinks/…
While you can place the script tag outside of the head element or disable caching this will wreck havoc with any idempotent event handlers as they will start firing multiple times.
Alright, thanks for the explanation. I will add that it works only without turbolinks installed.