not an ai
not an ai

Reputation: 1823

How to do per-page javascript with the Rails asset pipeline

I understand that for performance reasons it is better to let the asset pipeline concatenate and minify all my javascript and send the whole lot with every page request. That's fair enough

However, a bunch of my javascript is things like binding specific behaviours to specific page elements - stuff like

$('button').click(function(e) { $('input.sel').val(this.name); }

and I would feel more comfortable if I knew that this code was being executed only on that page - not on evey other page which might coincidentally have elements with the same IDs or which matched the same selectors How do people deal with this?

I would rather not put all this stuff inline in elements, just because when it gets to be more than about two lines long, keeping javascript correctly indented inside an .html.erb file is more work than it needs to be

Upvotes: 15

Views: 4060

Answers (5)

superluminary
superluminary

Reputation: 49232

For page specific JavaScript, I typically do something like this:

Application Helper

In the application helper I create a class attribute (though you could just as well use a data attribute instead).

module ApplicationHelper
  def body_attributes
    controller = params[:controller].gsub('/', ' ')
    action = params[:action]
    version = @version ? "version_#{@version}" : nil
    {
      class: ([controller, action, version] - [nil]).join(' ')
    }
  end
end

Note I'm also adding a version string. This helps with Google content experiments, and makes A/B testing a breeze.

Application.html.haml

In my global layout file, I do something like this to insert the attributes on the body tag:

!!! 5
%html
  %head
    ...
  %body{body_attributes}

script.js

Now in my page specific script, I just check for the class attributes, like this:

$(function () {
  if ($('body.pledge.new, body.pledge.create').length > 0) {
    // do work here...
  }
});

The advantage of this method is that getting the body by class is very quick. The script inside the conditional will not be executed at all on any page apart than the ones I choose, so minimal overhead, and I don't need to change my selectors throughout the code.

EDIT

Note that this answer is now 3 years old. You should be using client-side routing with a framework like React instead.

Upvotes: 1

Ineu
Ineu

Reputation: 1373

Here is what I do (based on some stackoverflow answers):

application_helper.rb

def body_page_name
  [controller_name.classify.pluralize, action_name.classify].join
end

application.html.haml

  %body{data: {page: body_page_name}}

application.js

$(function() {
  var page = $("body").data("page");
  if("object" === typeof window[page])
    window[page].init();
});

And in appropriate js file there's an object called ControllerAction:

tickets.js

var TicketsShow = new function() {
  var self = this;

  self.init = function() {
    // code which may call other functions in self
  };
};

There's probably better way to do it, but this works for me

Upvotes: 17

not an ai
not an ai

Reputation: 1823

I'll describe what I currently do, just in case it gives anyone a better idea

1) I changed the 'body' tag in my application.html.erb to add the current controller and action as data- attributes

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

2) I test this at the top of the relevant javascript

$(document).ready(function() {
        if($('body').data('controller')=='stories') {
            $('.story').click(function(e) {
                    var u=$(this).data('url');
                    u && (document.location=u);
            });
        }
    });

I can't decide if I think this is a good idea or not

Upvotes: 4

Kristian
Kristian

Reputation: 21840

I've done it and seen it done in several different ways:

  1. Rigging up the mvc to be able to load a particular js file per page, named along the same lines as a controller file. Like: <controller-name>.js

  2. Making a url parser in JS and then setting a global variable to the current page: UrlParams.currentView = 'dashboard'; and then saying if(UrlParams.currentView == 'dashboard') { //do specific js here }

  3. Setting a unique identifier as the page class or ID and then targeting that with your JS selectors. $('#dashboard').xyz();

Upvotes: 0

Diodeus - James MacFarlane
Diodeus - James MacFarlane

Reputation: 114437

I'd add a class to the BODY tag, allowing you to identify each page, and therefore each control per page.

<body class='page1'>

JS:

$('.page1 button').click(function(e) { $('input.sel').val(this.name); }

Upvotes: 0

Related Questions