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.

11
  • Is this a duplicate of stackoverflow.com/questions/8108511/… ? Looks like the accepted answer there would help. Commented Dec 15, 2011 at 1: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. Commented Dec 15, 2011 at 1: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. Commented Dec 15, 2011 at 2:20
  • Gonna be controversial here and ask why do you need such access? Commented Dec 15, 2011 at 4: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. Commented Dec 15, 2011 at 4:42

6 Answers 6

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

Sign up to request clarification or add additional context in comments.

2 Comments

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.
The second option looks really good. Have to remember that one.
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.

4 Comments

Rudolph, open the RailsCast and it also shows code examples. Better examples than I could ever give :)
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)...
The techniques mentioned are the same as those for passing variables to Js in views: stackoverflow.com/questions/2464966/…
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.
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.

Comments

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")

Comments

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

Comments

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 %>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.