Page-specific JavaScript in Rails 4+

I've seen a few different ways to use page-specific JavaScript in Rails, but most of them involve using jQuery to look for data-* attributes that correspond to your controller and action then fire on $(document).ready.

This is fine, but most of them ignore the possibility of having controllers with the same name (eg: users and admin/users) and will run the code on both controllers. Some try to skirt around this by using RegEx to parse the current URL and glean a controller and action from that (this approach is terrible. Never do it).

You can use Rails 5.1's new javascript_pack_tag, but that only works with Webpacker (as of me writing this article) and it enforces a style of having one JS file per view, which will result in a huge number of JS files very quickly. Finally, you have to include that tag in every view. Not ideal.

This led me to create a plain-JavaScript, IE9+ compatible, Turbolinks-agnostic Gem to allow easy page-specific JavaScript.

Punchbox 👊

As said above, Punchbox is an IE9+, Rails 4+, Turbolinks-agnostic, vanilla-Javascript (no jQuery 🎉), object-oriented way of running page-specific Javascript.

It's syntax is a modern spiritual successor to Paloma and it's inspired by another project I've contributed on, seed_tray.

That's enough talking about it, let's write some code!

First, install it like usual:

gem 'punchbox'

In your Gemfile, then

bundle install

Add it to your application.js anywhere above tree

// ...
//= require punchbox
//= require_tree .

Finally, add Punchbox's hook to your <body> tag

<body <%= punchbox_attributes %>>
  <%= yield %>
</body>

And you're ready to go!

Writing some JavaScript

I'm going to make the assumption that you're using a buildtool to transpile your ES2015+ code. As such, we'll be using JS class syntax. You can use several other syntax styles, as outlined in Punchbox's docs.

I'm also going to assume that we are working in a controller Posts. If you need more info on namespacing or similar, check out the docs.

Without further rambling, let's make a class! I'll be commenting it as it goes.

class Post { // The name of this class doesn't matter, nor does the filepath
  constructor() {
    // Code related to this class' setup.  Not required for Punchbox to run
  }
  controller() {
    // Code within here will run on every action of the controller
  }
  index() {
    // This code will run on the Posts#index action specifically
  }
  account() {
    // You aren't constrained to using REST actions either
    // This works with any action defined in your Rails controller
  }
  sharedMethod() {
    // You can have any other methods in here and reference them from other methods in this class!
    // Just make sure your method names don't conflict with action names
  }
}

// Enter the controller name, then the class
// The controller name is PascalCase with slashes preserved (see docs)
// Notice that the class isn't instantiated.  Punchbox handles that
Punchbox.on('Posts', Post);

That's it! Pretty simple, right? Plus it's a lot more robust than most other methods you see recommended. You don't have to use classes, you can use functions and modify their prototype or just a plain Object.

Punchbox automatically waits for the document to be ready, so you don't have to listen for DOMContentLoaded, turbolinks:load, $(document).ready(), or similar.


If you have any feedback or want to contribute, please make an issue/PR on the repo! Feedback is always appreciated.