15

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

telent
  • 1,813
  • 15
  • 21
  • possible duplicate of [Using Rails 3.1, where do you put your "page specific" javascript code?](http://stackoverflow.com/questions/6167805/using-rails-3-1-where-do-you-put-your-page-specific-javascript-code) – Ciro Santilli OurBigBook.com Aug 14 '14 at 09:42

5 Answers5

17

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

coorasse
  • 5,278
  • 1
  • 34
  • 45
Ineu
  • 1,363
  • 9
  • 15
  • This was #1 in my response. I've actually never seen it done in rails, because most of my mvc work is in PHP, in which i've implemented this, but I'm voting you up for the code snippet! Cheers – Kristian Jan 23 '12 at 18:24
  • You get an upvote for the module, I think. That makes it a bit cleaner than my conditionals – telent Jan 23 '12 at 18:36
  • Thank you guys. @Kristian it really more like #3 in your response. #1 is not the case with rails' assets pipeline, because all javascripts compiled into single file so including separate file make assets useless. – Ineu Jan 23 '12 at 19:36
4

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

telent
  • 1,813
  • 15
  • 21
1

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.

superluminary
  • 47,086
  • 25
  • 151
  • 148
0

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); }
Diodeus - James MacFarlane
  • 112,730
  • 33
  • 157
  • 176
0

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();

Kristian
  • 21,204
  • 19
  • 101
  • 176