41

In Rails 3.1 it is not possible to access controller instance variables in an asset js.erb or coffee.erb file using syntax such as <%= @foo %>, where @foo is set in the controller. So then the question is what are the best ways for passing controller variables to CoffeeScript or JavaScript assets.

This question has kind of been asked in multiple convoluted forms on the forum, but my point in asking it again is to have a place where all recommendations are gathered together, and the code supplied is simple and readable. Also note that I'm specifically referring to assets and not view response files.

Safa Alai
  • 1,223
  • 1
  • 15
  • 27
  • Is this a duplicate of http://stackoverflow.com/questions/8108511/how-to-access-instance-variables-in-coffeescript-engine-inside-a-slim-template ? Looks like the accepted answer there would help. – Thilo Dec 15 '11 at 01:20
  • @Thilo As I understand it that question refers to a .slim template in the views folder, so in effect the CoffeeScript is being processed as a part of the view rather than as an asset file in advance. Please correct me if I'm wrong. – Safa Alai Dec 15 '11 at 01:27
  • Yeah, I'm not familiar enough with Rails to be sure if that answer applies. But the gist seems to be to render all instance variables that you are going to need into a piece of Javascript, that the other assets can then refer to. – Thilo Dec 15 '11 at 02:20
  • Gonna be controversial here and ask why do you need such access? – Srdjan Pejic Dec 15 '11 at 04:38
  • 2
    Also, the second answer from the related question actually shows what I think is a better practice. Store whatever data you need in a data attribute of you really must need it. Also, consider responding with JSON to whatever is requesting a non-HTML template from your rails action. – Srdjan Pejic Dec 15 '11 at 04:42
  • @SrdjanPejic The CoffeeScript does some setup based on certain values in the database/model. The controller decides what values to dig out of the database/model. – Safa Alai Dec 15 '11 at 04:43
  • Not to be obtuse, but what? Can you elaborate? – Srdjan Pejic Dec 15 '11 at 04:47
  • @SrdjanPejic for example, there might be certain parameters that are specific to a user: score, weight etc. These parameters are then used by the CoffeeScript to calculate various things for the user in order to get the application ready. – Safa Alai Dec 15 '11 at 04:58
  • Again, sounding obtusely, why is the controller processing data and handing it off to coffeescript for further processing? What is coffeescript doing that cannot be done in the controller and regular view? If it's an Ajax request, why not just ship json and do all of the processing client-side? – Srdjan Pejic Dec 15 '11 at 05:08
  • @SrdjanPejic the CoffeeScript does calculations based on user input. These calculations are fast in and real time, but they do depend on the user's profile. Consider this however: I have a complex CoffeeScript program that runs on the user's browser (a game), and this program is initialized with certain information that is in the database. – Safa Alai Dec 15 '11 at 05:17
  • possible duplicate of [Ruby on Rails - Send JavaScript variable from controller to external Javascript asset file](http://stackoverflow.com/questions/2721880/ruby-on-rails-send-javascript-variable-from-controller-to-external-javascript) – Ciro Santilli OurBigBook.com Nov 02 '14 at 12:34

6 Answers6

28

a couple of ways I have done this in the past

put the data in hidden fields, access the data in js/coffee

# single value
<%= hidden_field_tag "foo_name", @foo.name, { :id => "foo-name" } %>
$('#foo-name').val();

# when the 'value' has multiple attributes
<%= hidden_field_tag "foo", @foo.id, { :id => "foo", "data-first-name" => @foo.first_name, "data-last-name" => @foo.last_name } %>
$foo = $('#foo')
console.log $foo.val()
console.log $foo.data("firstName")
console.log $foo.data("lastName")

another option: load data into js data structure in erb, access it from js/coffee

<% content_for(:head) do %>
    <script>
    window.App = window.App || {};
    window.App.Data = window.App.Data || {};
    window.App.Data.fooList = [
        <% @list.each do |foo| %>
            <%= foo.to_json %>,
        <% end %>
    ];
    </script>
<% end %>


# coffee
for foo in window.App.Data.fooList
    console.log "#{foo.id}, #{foo.first_name} #{foo.last_name}"

I am not a big fan of constructing javascript data from ruby in erb like this, something about it just feels wrong - it can be effective though

and another option: make an ajax call and get the data on-demand from the server

I am also interested in other ideas and approaches

house9
  • 20,359
  • 8
  • 55
  • 61
  • If you need to run loops and construct json you could use a before_filter which runs a method to construct a single JSON object and assign it to an instance variable. Then you simply output the one instance variable in your view's Javascript assignment. I find this better than ajax because it's one less HTTP request. – BradGreens Jun 25 '13 at 19:27
  • The second option looks really good. Have to remember that one. – Daniel Viglione May 12 '16 at 23:20
9

There is a really nice rail cast and quite recent (feb. 2012) about this specific topic: #324 Passing Data to JavaScript

It shows 3 ways: a script tag, a data attribute, and the Gon gem. I think house covered all the available techniques. I would only mention that using an AJAX call is best used when you have a large volume of data, dynamic data or combination of both.

Max
  • 6,563
  • 4
  • 25
  • 33
  • 1
    Rudolph, open the RailsCast and it also shows code examples. Better examples than I could ever give :) – Max Jul 30 '13 at 10:02
  • 1
    I opened, I saw, but answers are supposed to be self contained with all pertinent example code (i.e. _not_ require you to access another page in order to get pertinent information)... – rudolph9 Jul 30 '13 at 22:27
  • The techniques mentioned are the same as those for passing variables to Js in views: http://stackoverflow.com/questions/2464966/passing-ruby-variables-to-javascript-function-in-rails-view/24456817 – Ciro Santilli OurBigBook.com Oct 06 '14 at 12:23
  • This was the best answer. I added an answer that included examples using gon, but my edit approval rating is so low I just reposted it as an answer. – Ninjaxor Feb 18 '15 at 16:31
5

Rather than use a hidden field I chose to add a data attribute to the container div which jquery can pick up.

<div class="searchResults" data-query="<%= @q %>"></div>

then the jquery to access it

url: "/search/get_results?search[q]=" + $(".searchResults").data("query") + "&page=" + p

I feel this is the cleanest way to pass data to javascript. After having found no way to pass a variable to a coffee script file with the rails asset pipeline from a controller. This is the method I now use. Can't wait till someone does set up the controller way with rails that will be the best.

Dave Robertson
  • 383
  • 4
  • 13
3

In the controller:

@foo_attr = { "data-foo-1" => 1, "data-foo-2" => 2 }

In the view (HAML):

#foo{@foo_attr}

In the CoffeeScript asset:

$("#foo").data("foo-1")
$("#foo").data("foo-2")
kxmh42
  • 3,121
  • 1
  • 25
  • 15
1

In situations where your javascript data gets out of hand, using the gon gem is still the preferred way to go in rails, even in 2015. After setting up gon, you are able to pass data to your javascript files by simply assigning the data to the gon object in rails.

(Gemfile)
gem 'gon'

(controller) 
def index 
  gon.products = Product.all 

(layouts) 
<%= include_gon %> 

(public/javascripts/your_js_can_be_here.js) 
alert(gon.products[0]['id'); 

(html source automatically produced) 
<script> 
  window.gon = {}; 
  gon.products = [{"created_at":"2015", "updated_at":"2015, "id":1, "etc":"etc"}];

You can read more verbose implementation details on Gon or the two other rails-javascript channels from Ryan Bate's screencast.
http://railscasts.com/episodes/324-passing-data-to-javascript

Ninjaxor
  • 876
  • 12
  • 27
0

You can edit and add variables to the params array in the controller then access them in the response.js.erb. Here's an example with params[:value]:

def vote
  value = params[:type] == "up" ? 1 : -1
  params[:value] = value
  @public_comment = PublicComment.find(params[:id])

  have_voted = @public_comment.evaluators_for(:pub_votes_up) << @public_comment.evaluators_for(:pub_votes_down)

  unless have_voted.include?(@current_user) # vote
    @public_comment.add_or_update_evaluation(:"pub_votes_#{params[:type]}", value, @current_user)
  else                                      # unvote
    @public_comment.delete_evaluation(:"pub_votes_#{params[:type]}", @current_user)
    params[:value] = 0
  end

  respond_to do |format|
    format.js # vote.js.erb
  end
end

And here's an example accompanying response.js.erb

button = $('<%= ".pub#{params[:type]}_#{params[:id]}" %>')
label = button.find('strong')
<% comment = PublicComment.find(params[:id]) %>
label.html('<%= comment.reputation_for(:"pub_votes_#{params[:type]}").to_i %>')

<% if params[:value] == 1 %>
  button.addClass('btn-success')
<% elsif params[:value] == -1 %>
  button.addClass('btn-danger')
<% else %>
  if button.hasClass('btn-success') { button.removeClass('btn-success') }
  if button.hasClass('btn-danger') { button.removeClass('btn-danger') }
<% end %>
Archonic
  • 5,207
  • 5
  • 39
  • 55