programming_ritual
programming_ritual

Reputation: 188

How to write JavaScript for Ruby On Rails?

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:

Upvotes: 0

Views: 2201

Answers (3)

Hiren Bhalani
Hiren Bhalani

Reputation: 848

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.

Upvotes: 1

max
max

Reputation: 102443

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.

Upvotes: 0

Joel Blum
Joel Blum

Reputation: 7888

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.

Upvotes: 1

Related Questions