0

Let's say I have a website which has the following pages: Home About Friends.

What I want to do is create a simple JavaScript file (called test.js), so when a user has pressed a button I can do something simple such as :

function myFunction(name) {
  console.log("Name = " + name);
}

Now here comes the part where I have my questions. I noticed that ruby uses project_name/app/views/layout/application.html.erb and provides the same <head> for all three pages (Home, About and Friends).

However, that's not what I want to do here! I only want the test.js to work for Friends page!

Do I reference that in my project_name/app/views/home/friends.html.erb as shown below:

<!-- At the bottom of friends.html.erb -->
<script type="text/javascript" src="directory-to-javascript-file"></script>

Also I have noticed that there is a JavaScript folder under this directory project_name/app/javascript which contains two folders; channels and packs.

Is this where I want to add my test.js, and if so in which folder?


So to summaries:

  • In which folder do I have to save my test.js file?
  • Where to reference test.js so that it is only visible by Friends Page?
1
  • "I noticed that ruby uses project_name/app/views/layout/application.html.erb" this is all Rails and has very little to do with the language. Commented May 6, 2021 at 13:16

3 Answers 3

1

You have 2 choices for to put the js, the assets pipeline or webpack. The assets pipeline is maybe a bit simpler to understand, in app/assets/javascripts/application.js you can simply put your test function after all the require statements (or extract it to a standalone file and require it). Make sure your application.html.erb has a <%= javascript_include_tag 'application'%>

You do not need to put a script tag in friends.html.erb, remove it and let the layout and asset pipeline / webpacker take care of it for you

Second option is to use webpacker. There are quite a few resources written about how to use it with Rails, it's probably better to get familiar with it eventually, I just thought for beginners maybe the old assets pipeline is a bitter easier; the webpack process is very similar though so once you understand one you will easily be able to transfer to the other.

Where to reference test.js so that it is only visible by Friends Page?

You can create a new layout for the friends controller.

class FriendsController < ApplicationController
  layout "friends"
end

So in your layouts dir create a friends.html.erb. In friends.html.erb use<%= javascript_include_tag 'friends' %>. Next create a manifest (or pack) file for friends: app/assets/javascripts/friends.js and require whatever you need there. This will mean friends controller will get its own layout with its own separate javascript.

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

3 Comments

The drawbacks here is that you get an extra http request and the issues involved with a having a persistent browser session - say with turbolinks or Server-Side Concerns which causes the JS to "leak" into contexts you didn't intend. You also don't really need to use layouts - you can use the the captures helper or even script tags in the view instead which causes less code duplication.
Also when dealing with webpacker you also need to take additonal steps to make that function definition a global so that you can use it in inline JS.
I agree with @max that this has drawbacks in case you want everything to part of "the same system" e.g using a thing like turbolinks. So its a question of how different these 2 parts are, if they're almost identical go with a yield/capture maybe stackoverflow.com/questions/2464595/…
1

I think this is what you are looking for.

you mentioned that you have directory pack in app/javascript, seems you are using web packer for js and css.

so here is the solution for you.

create a test.js inside the pack directory.

And write following in to the project_name/app/views/home/friends.html.erb that you mentioned in your question.

<%= javascript_pack_tag 'test' %>

and write all your js code in test.js file. and that will be loaded into only mentioned page.

If you want to same thing with other areas like home and about. create js files for that and include the <%= javascript_pack_tag 'js file name' %> into the appropriate html.erb file.

This will help you have same layout for view of every module, but still have it's own uniq js that required only for specific view.

You might need to restart the rails server.

Hope my answer helps.

Comments

0

In Rails you really want to forget the concept of "I just want to load this JS file on this page" - I would consider it an anti-pattern that will hold you back.

Rails uses both an assets pipeline / webpacker to streamline your JS delivery, turbolinks to "ajaxify" regular page loads in older versions and Rails UJS does a lot of ajax trickery. Which means that you very likely will be dealing with ajax and persistent browser sessions which make the idea into a real problem as your JS leaks into contexts you didn't intially imagine or just plain won't work unless you're reloading the whole page.

Instead think in terms of event based programming and behaviors that you can augment UI elements with. Think "I want this kind of button to do X" instead of "I want this specific thing on this page to do X". Use event handlers and css-classes to reach that goal:

document.addEventListener("click", (event) => { 
  if (!event.target.matches('.magic-button')) return;
  console.log('You clicked a magical button!');
});

This makes your JS decoupled from the page itself and reusable and actually makes your intial problem moot. If you don't want to behavior on other pages then don't add the magic-button class.

If you REALLY want to do "per page" javascript one approach that I have used it to attach the controller_name and action_name to the body element:

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

That lets you know where you are in your application simply by reading the data attributes:

// given controller = foos and action = bar the events are:
// * MyApp:loaded
// * MyApp:foos#bar
// * MyApp:foos
// * MyApp:#bar
const firePageEvents = (event) => {
  // read the data attributes off the body element
  const body = document.querySelector('body');
  const details = {
    action: body.dataset.action,
    controller: body.dataset.controller
  };
  const eventNames = [
      "MyApp:loaded", 
      `MyApp:${controller}#${action}`, 
      `MyApp:${controller}`, 
      `MyApp:#${action}`
  ];
  // fire the events
  eventNames.forEach((name) => {
     let event = new CustomEvent(name, { detail: details });
     document.dispatch(event);
  });
}

// fires the events either when turbolinks replaces the page
// or when the DOM is ready
if (window.Turbolinks && Turbolinks.supported) {
  document.addEventListener("turbolinks:load", (event) => { 
    firePageEvents(event);
  });
} else {
  document.addEventListener("DOMContentLoaded", (event) => { 
    firePageEvents(event);
  });
}
// this will be trigged when any page is loaded
document.addEventListener("MyApp:loaded", (event) => {
  switch(event.details.action) {
    case 'about':
      console.log('about page loaded');
      break;
    case 'friends':
      console.log('friends page loaded');
      break;
  }
});

// this will be trigged when the an action named "about" is rendered:
document.addEventListener("MyApp:#about", (event) => {
  console.log('About page loaded', event.details);
});

// this will be triggered on the users load page
document.addEventListener("MyApp:users#index", (event) => {
  console.log('User#index page loaded', event.details);
});

We then fire custom events that you can attach event listeners to make JS fire for specific controllers / actions.

This code belongs in your assets pipeline / packs where its effectivly concatenated and delivered.

1 Comment

I did do a jQuery based gem (pagescript) years ago that I havent actively maintaineded.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.