how to passing data with javascript in rails [duplicate] - ruby-on-rails

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.

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

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.

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.

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

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

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

Related

Rails how do I pass a rails variable to CKEditor config?

My scenario is that, some user who has the font_user role can use font, but others can't.
In ckeditor/config.js, I can't get any variable from Rails. How do I achieve this?
I have tried something like this:
Modify config.js to config.js.erb.
Add the following code.
<% current_user.has_role?(font_user) %>
XXXXX
<% else %>
XXXX
<% end %>
and I added the following method in application_controller.rb:
helper_method :current_user
But it seems config.js.erb can't get the current_user variable.
This is because asset JavaScript is compiled ones. Not each time a view is rendered. You should make use of unobstructive JavaScript. For example:
View example:
<div class="ckeditor" data-ckeditor-font="<%= current_user.has_role?(font_user) %>"></div>
Alternatively (not checked):
You could also use the #content_tag for this, although you have to check how to exactly pass along the data attributes. If I'm not mistaken:
<% data = {
'ckeditor-font': current_user.has_role?(font_user),
# other data...
} %>
<%= content_tag :div, class: 'ckeditor', data: data %>
But I currently don't have the setup to test the above code. So you'll have to check it yourself.
Than in your asset JavaScript (CoffeeScript):
initCkeditor = (element) ->
fontUser = element.dataset.ckeditorFont == 'true'
# further CKEditor initialization code...
document.addEventListener 'turbolinks:load', ->
document
.querySelectorAll '.ckeditor'
.forEach initCkeditor
If you don't set the data-ckeditor-font attribute the code still works. Since element.dataset.ckeditorFont would return undefined, and undefined == 'true' is still false. (Keep in mind that the CoffeeScript == equals JavaScript ===.)
See:
Working with JavaScript in Rails: Unobtrusive JavaScript
The Asset Pipeline for more info about the asset pipeline.
Using data attributes for more info about data attributes.

Rails template set value to array of strings

I have an angular module where I set a few strings. I'd also like to set an array of strings in the template as well.
application.html.erb
<script type="text/javascript">
angular.module('userFromServer', [])
.service('currentUser', function() {
<% if logged_in %>
this.name = '<%= #User.name %>';
this.friends = '<%= #User.profile.friends_by_uuid %>';
<% end %>
})
</script>
controller
def friends_by_uuid
self.friends.map{|x| "puser_#{x.uuid}"} # also tried adding .to_json
end
However the output appears to have some escaping issues.
"["puser_589b07ee-b8f1-4214-941d-0ce0b7a6703b", "puser_ec7d2918-d514-4c42-91fc-641ed2958fcd"]"
var desired_output = "["puser_589b07ee-b8f1-4214-941d-0ce0b7a6703b", "puser_ec7d2918-d514-4c42-91fc-641ed2958fcd"]
How can I render an array of strings in a rails template?
You should not put logics in views or controllers: Is it bad to have fat views in rails? Fat models are expected. So the solution is chunk the array in models / using helper functions, feed it to controller, and finally use in views. Since no idea what exactly you want, this is the most I can do.
For example:
# controller
def index
#ids = MyModel.split(params[:ids]) # suppose params[:ids] are the data coming in
end
# view
<% #ids.each do |id| %>
# ...
<% end %>
For ruby to process strings, many posts may help. For example, here.

Using Mustache lambda functions in ERB

I'm trying to share my Mustache templates in Rails across the server and the client on the lines of this Railscast.
All is well, except that I'm unable to figure out where and how to put the definition of a lambda function on the server side.
Let's say my html.erb looks like:
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => #post %>
<% else %>
<script type="text/template" id="template">
<%= render 'template' %>
</script>
<% end %>
The Mustache handler looks like this (exactly as in the Railscast):
module MustacheTemplateHandler
def self.call(template)
if template.locals.include? 'mustache'
"Mustache.render(#{template.source.inspect}, mustache).html_safe"
else
"#{template.source.inspect}.html_safe"
end
end
end
ActionView::Template.register_template_handler(:mustache, MustacheTemplateHandler)
Now for the following template:
<h1>{{title}}</h1>
<div>
{{#marked}}{{content}}{{/marked}}
</div>
the lambda marked is easy to handle in JavaScript, but how can I define it in my Rails code to render content using Redcarpet?
Update
Since posting this, I have tried to expand on the idea of helper functions in the screencast. I now have
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => process(#post) %>
<% else %>
...
The process is defined in ApplicationHelper as
def process(obj)
{
marked: lambda {|text| markdown(Mustache.render(text))}
}
end
This has two problems:
text inside the (Ruby) lambda function is indeed '{{content}}', but Mustache.render(text) fails to do anything with it — it's returning an empty string.
The above code will now only render the marked field and I haven't been able to find a way to retain the other (unprocessed) attributes of the author object (e.g. title). obj.attributes seems like a promising start, but I don't know how to combine the processed response for marked with the other attributes even if #1 above worked.
I got this working myself. The process method in ApplicationHelper now looks like this (using the new lambda syntax):
def process(obj)
obj['marked'] = ->(text) { markdown(Mustache.render(text, obj)) }
obj.attributes
end
This will now catch all invocations of marked in any template.

Rendering rails partial with dynamic variables

I'm trying to render a partial based on the taxon the user is inside. In my application.html.erb layout I have the following line of code:
<%= render 'spree/shared/women_subnav' if #enable_women %>
In the taxons controller, inside the show method, I have:
#taxon_id = params[:id].split('/').first
And in taxons#show I have:
<% if #taxon_id == params[:id].split('/').first %>
<%= "#enable_#{#taxon_id}" = true %>
<% end %>
When I run this I get a SyntaxError. But in taxons#show If I just enter:
<% if #taxon_id == params[:id].split('/').first %>
<%= "#enable_#{#taxon_id}" %>
<% end %>
without the '= true' then the page renders, outputting '#enable_women'. So I know it's getting the correct variable, I just need that variable to be set to true. What am I missing?
Thanks so much.
First of all I would like to give you some heads-up:
calling first on a user submittable input is not a great idea (what if I submit ?id=, it would return nil) also non utf-8 encoding will crash your app such as: ?id=Ж
Controllers are beast! I see you are setting the value of a true/false instance_variable in the view, please use controllers do define the logic before rendering its output. especially when parameter dependant.
so for a solution:
in your controller as params[:id] should suggest an INT(11) value:
def action
# returning a Taxon should be a good idea here
#taxon = Taxon.find(params[:id])
# as I would give a Taxon class an has_many relation to a User
#users = #taxon.users
end
and in your action's view
<%= render :partial => "taxons/users", collection: #users %>
of course you would have the great ability to scope the users returned and render the wanted partial accordingly.
if you want more info about "The Rails way" please read:
http://guides.rubyonrails.org/
Have fun!
use instance_variable_set
instance_variable_set "#enable_#{#taxon_id}", true
just a reminder that it's better to do these things inside a controller.

Rails resource with AJAX

In rails, what kind of AJAX call would need to be made in order to create and modify resources. Say if i had a resource like this
Man(age: integer, country_from:string, residence:string)
Now how would this be made through an AJAX call (like sending post to my create function, how would parameters be sent in, how would my controllers be setup). Be detailed, my AJAX is very, very, very weak. (right now i have my Man made like rails generate scaffold Man age:int country_from:string ...)
PS
Im using rails 3
So I believe there are two sides to this: the javascript and the controller changes.
In your controller you need to ensure it can return json output (or xml or whatever your chosen ajax-y output is):
def man
# do your work
return_data = {}
# initialize your return data
respond_to do |format|
render :json => return_data.to_json, :layout => nil
end
end
There are many ways to generate your json output but basically you have to make sure it's in a shape that is easily consumed on the view javascript.
I use jQuery and here's the code to execute an ajax call:
function foo(some_param) {
$.ajax({
type: 'GET',
url: "/<controller>/man?FOO=" + some_params,
dataType: 'json',
success: handle_success,
error: handle_errors
}
function handle_success(data) {
# process return JSON. it's a javascript object corresponding to the shape
# of your JSON. If your json was a hash server side, it will be an 'object', etc
}
function handle_error(data) {
# handle error cases based upon failure in the infrastructure, not
# failure cases that you encounter due to input, etc.
}
You can tie the foo function to some button or onclick as you desire.
I am not sure this is complete enough. Let me know if you need more detail, etc.
Rails 3 can help by telling the form that you want it to be "remote" (ajax)
<%= form_for #man, :remote=>true do |f| %>
<div class="field">
<%= f.label :man %>
<%= f.text_field :man %>
</div>
<%= f.submit "Save", :disable_with=>"Saving..."%>
<% end %>
in your Controllers
class MansController < ApplicationController
respond_to :js, :html
def update
#man = Man.find(params[:id])
#man.update_attributes(params[:man])
respond_with #man
end
end
Then, you can have
/app/views/mans/update.js.erb
<% if #man.errors.any? %>
alert("This is javascript code that either renders the form in place of the other form");
<% else %>
alert("success!")
<% end %>
Note: Wherever I say "mans" above, it might be "men"

Resources