Passing a local variable into another view's JS - Rails - javascript

Is there a way to grab / fetch a variable inside js between two different views? Say I have an index view with does:
<% foo.each do |foo| %>
<%= link_to 'bar', bar_path %>
<% end %>
Then in bar I want to add some jQuery to a specific element with foo.id selector:
$('div#<%= foo.id %>').fadeOut(); // I know the interpolation wouldn't work here
I know I can pass foo.id to bar with my path or send it via the locals hash, but how would I "grab" it with jQuery?

You can easily pass variables to JS using a gem called gon.
gem "gon"
All you have to do is add this gem to you Gemfile and then you can call gon inside any controller like this:
class PaymentsController < ApplicationController
def new
...
gon.controller = "payments"
gon.action = "new"
gon.message = "Hello World!"
...
end
end
And now, at any javascript (in following case, coffeescript) you can:
$ ->
# Check if it is the target controller and action
if gon? && gon.controller is "payments" && gon.action is "new"
alert gon.message

Is there a way to grab / fetch a variable inside js between two
different views
Not without hacking.
JS is an client-side technology, meaning it loads in the browser, not on the server. This is famous for causing so many issues for developers trying to bind to dynamically-named objects, much like what you're trying to do.
There are several ways around this. I'll go through them for you:
Class
The first way is to use a broader identifier, such as element type or class.
You're currently trying to select foo.id, which means you're going to have to pass that data directly to JS somehow. Inefficient.
A much better way would be to use something like this:
$("a.bar").on("click", function(e){
$(this).fadeOut();
});
--
URL
According to this resource, there are no inbuilt ways to pull query strings from JS: How to get the value from the GET parameters?
You may be able to do something like this:
$(document).ready(function(){
foo = window.location.search.substring(1); //Considering you have http://url.com/your/foo/id
$("#" + foo).fadeOut();
});
--
ERB
There is a final alternative, although this only works if you don't precompile your JS (which I'm not sure is actually feasible any more). That is to use ERB directly in your JS, rather like what you've done in the question.
Whilst this works for dynamic references (such as paths), I don't think it can pull data from a controller. Unless, of course, you put the script directly in your view - which goes against the conventions of MVC.
#app/views/bar/show.html.erb
<script>
// your code here
</script>

I think you need to create a js.erb file and in that pass a variable defined in your controller action.
The code insde js.erb would look like this :
<% fields_html = render "fields", :entity => #model %>
$("#fields-list").html("<%= escape_javascript fields_html %>")
Here fields is a partial I have created. :entity is basically passing of the variable value. #model is the variable defined inside controller action. fields-list is the id of the field I want to load using jquery.

Related

How to get a Rails variable into JavaScript file (D3)

I need to grab data from by database and pass it to JavaScript file in my Rails 5 app.
In my controller I create a variable that looks like:
#this_anomaly = Anomaly.find_by_id(2)
I can obviously access it as well as in my view with something like:
<%= #this_anomaly.started_at %>
But I need to get that same data into my javascript file, which I use to draw graphs in D3 to inject into my view. How do I pass it through?
Not sure where to start, thanks!
You could declare your javascript variable in your html.erb like
var started_at = <%= #this_anomaly.started_at %>
One option is to put the data in your HTML in a data attribute
<div data-started-at=<%= #this_anomaly.started_at %> data-for-d3-graphics></div>
Then in your js file
let value = $('div[data-for-d3-graphics]').data('started-at');
I'm not sure if that is the exact syntax for .erb files, but you get the idea
So one method that I tried that works (since I'm using d3) is to request the data in my html.erb with:
<div id="anomaly_data">
<%= #this_anomaly.started_at %>
</div>
Then in my js file, use d3.select to store it in a variable:
var anomaly = d3.select("#anomaly_data").text();
Which works and gets me the date/times/etc without putting them through any processing! But, there must be a better way?

Rails - how put javascript variable into erb code

I've seen some similar question, but still don't know how achieve my goal.
I want to do that thing:
Display modal with products represented by divs.
User choose some products (I add .active class to chosen products)
Then I use jQuery to make array of chosen products ids.
In the end I want to create div for each product, which will be include some informations about this product from database. I'm using js .append() to do it. And here is a problem.
This code is in my script tag in proper view.
var chosen_products_array;
$('.product-to-choose').click(function() {
$(this).toggleClass("active");
$(this).find('i').toggleClass("visible");
});
$('#chosen-products-confirm').click(function() {
var chosen_products = $('.product-to-choose.active');
chosen_products_array = jQuery.makeArray( chosen_products );
});
$('#confirm').click(function() {
var textToInsert = '<div><h4>Products:</h4>';
$.each(chosen_products_array, function(count, item) {
var id = $(item).attr('id').substr(8); // id is product_number
textToInsert += '<div><li name="chosen-product"> <%= current_user.products.find(' + id +').name %></li></div>';
});
textToInsert += '</div>';
$('#div-example').append(textToInsert);
});
My controller create action:
def create
#meal = current_user.meals.build(meal_params)
if #meal.save
current_user.type_tags.each do |type_tag|
key = "type#{type_tag.id}"
if params[key]
#meal.type_tags << type_tag # zapis do bazy danych
end
end
flash[:success] = "Create new meal!"
redirect_to meals_path
else
#render 'meals/new'
end
end
This line generates error:
textToInsert += '<div><li name="chosen-product"> <%= current_user.products.find(' + id +').name %></li></div>';
Error:
Couldn't find Product with 'id'=+id+ [WHERE "products"."user_id" = ?]
I know there is a problem in passing js variables to rails erb, but how can I solve it in this case? I tried with gon too.
In my controller, I added:
gon.products = current_user.products
and then try
alert(gon.products)
it's ok - array of objects, but when I try
alert(gon.products.find(1))
it doesn't work.
Have you got any idea how can I put this javascript id variable to erb code? Or maybe is any different solution?
Probably the best would be to add a remote form (it may be hidden) that posts whatever you want (javascript values) and then it is handled by a controller. Then you can render it with js views.
This would probably be the most "rails-like" solution.
Here's some code:
In the routes:
resource :my_form, only: :create
In the view:
<%= form_tag(my_form_path, remote: true, method: :post) do %>
<%= hidden_field_tag :val %>
<% end %>
You alter the val dynamically by javascript, and submit the form whenever you want to.
Then, in the controller you have something like this:
respond_to :js
def create
# do something with params['val']
#my_val = params['val']
end
And you would also add create.js.erb view, where you could add some javascript that can use any of the instance variables created in the controller.
//create.js.erb
$('#someElement').html('<%= #my_val %>');
Let me stress if it is not clear, the form is remote (ajax), so it is sent in the background and the page is not reloaded.
When the browser receives the response, it may contain any javascript you decide to render (the contents of create.js.erb), and then the browser executes that javascript.
So, you can add there any jquery commands with arguments that you changed dynamically by rails, or you could also render html partials and replace any elements with them, like this:
//create.js.erb
$('#my_id').html('<%= j(render partial: 'my_form/_some_partial.html.erb', locals: { my_val: #my_val }) %>');
More details on Working with JavaScript in Rails
UPDATE
Another, inferior but simpler to implement solution would be to always render all products, but have them hidden initially, and then show them by jQuery on demand. You would just need to add appropriate css classes or data attributes to those products, as well as the links you use to select them.
The problem is that erb code is evaluated on the server before the browser even fetches the javascript file, so it doesn't make sense to use this way. It's recommended that you only use erb in your javascript for simple things like asset_path
I think first make your model visible to the browser as js variable(json object) . Some thing like below at the end of your view file(below is the haml syntax):
:javascript
var userProducts = #{#current_user.products.to_json};
Then use it in javascript expression

How to assign and access a javascript variable in Rails using ajax

I'm using the gon gem for rails, which allows you to save variables defined in a controller action and use them in your JavaScript. It works fine when I use it in non-Ajax settings, however, I'm having an issue with using it successfully when doing Ajax requests.
The problem: Ruby variables I assign to gon variables in the controller action when making Ajax requests come out as 'undefined' in the JavaScript.
The objective: I want to trigger an Ajax request on my page, which:
1) hits an action in the controller, and assigns a Ruby variable to a gon variable.
2) it then renders a js.erb file which executes JavaScript, part of which needs to take the Ruby variable defined in step 1, and treat it as a js variable.
here's the example action in step 1:
def some_action
gon.my_ajax_var = {some: 'info'}
end
here's the example js.erb file it renders:
/some_action.js.erb
console.log('gon.my_ajax_var equals ' + gon.my_ajax_var) //this doesn't work! comes out as 'undefined' when I expected {some: 'info'}
Any thoughts on how I fix this? I took a look at the gon.watch page, but I was confused as to whether that relates to this problem I'm having and how to implement the correct solution. Additionally, if there's a better way to do this without gon, I'm open to that as well.
Thanks!
I ended up solving this by doing the following:
In my controller:
def some_action
#my_ajax_var = {some: 'info'}.to_json
end
In my corresponding view:
/some_action.js.erb
var my_ajax_var = <%= #my_ajax_var.html_safe %>
Would've been nice to have piggybacked off the gon gem, but this got the job done.
It's some time ago that I used erb templates, but I think you need to add tags in your erb-file.
/some_action.js.erb
console.log('gon.my_ajax_var equals ' + <%= gon.my_ajax_var %>)

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
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
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
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.
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); }
I've done it and seen it done in several different ways:
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
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 }
Setting a unique identifier as the page class or ID and then targeting that with your JS selectors. $('#dashboard').xyz();

How to stay DRY when using both Javascript and ERB templates (Rails)

I'm building a Rails app that uses Pusher to use web sockets to push updates to directly to the client. In javascript:
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append("<div class='tweet'><div class='tweeter'>"+tweet.username+"</div>"+tweet.status+"</div>");
});
This is nasty mixing of code and presentation. So the natural solution would be to use a javascript template. Perhaps eco or mustache:
//store this somewhere convenient, perhaps in the view folder:
tweet_view = "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>"
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append(Mustache.to_html(tweet_view, tweet)); //much cleaner
});
This is good and all, except, I'm repeating myself. The mustache template is 99% identical to the ERB templates I already have written to render HTML from the server. The intended output/purpose of the mustache and ERB templates are 100% the same: to turn a tweet object into tweet html.
What is the best way to eliminate this repetition?
UPDATE: Even though I answered my own question, I really want to see other ideas/solutions from other people--hence the bounty!
imo the easiest way to do this would involve using AJAX to update the page when a new tweet is created. This would require creating two files, the first being a standard html.erb file and the second being a js.erb file. The html.erb will be the standard form which can iterate through and display all the tweets after they are pulled from the database. The js.erb file will be your simple javascript to append a new tweet upon creation, i.e.:
$('#timeline').append("<div class='tweet'><div class='tweeter'><%= tweet.username %></div><%= tweet.status %></div>")
In your form for the new tweet you would need to add:
:remote => true
which will enable AJAX. Then in the create action you need to add code like this:
def create
...Processing logic...
respond_to do |format|
format.html { redirect_to tweets_path }
format.js
end
end
In this instance, if you post a tweet with an AJAX enabled form, it would respond to the call by running whatever code is in create.js.erb (which would be the $('#timeline').append code from above). Otherwise it will redirect to wherever you want to send it (in this case 'Index' for tweets). This is imo the DRYest and clearest way to accomplish what you are trying to do.
Thus far, the best solution I found was Isotope.
It lets you write templates using Javascript which can be rendered by both the client and server.
I would render all tweets with Javascript. Instead of rendering the HTML on the server, set the initial data up as JS in the head of your page. When the page loads, render the Tweets with JS.
In your head:
%head
:javascript
window.existingTweets = [{'status' : 'my tweet', 'username' : 'glasner'}];
In a JS file:
$.fn.timeline = function() {
this.extend({
template: "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>",
push: function(hash){
// have to refer to timeline with global variable
var tweet = Mustache.to_html(timeline.template, hash)
timeline.append(tweet);
}
});
window.timeline = this;
channel.bind('tweet-create', this.push);
// I use Underscore, but you can loop through however you want
_.each(existingTweets,function(hash) {
timeline.push(hash);
});
return this
};
$(document).ready(function() {
$('#timeline').timeline();
});
I haven't tried this, but this just occurred to me as a possible solution:
In your view create a hidden div which contains an example template (I'm using HAML here for brevity):
#tweet-prototype{:style => "display:none"}
= render :partial => Tweet.prototype
Your tweet partial can render a tweet as you do now.
.tweet
.tweeter
= tweet.username
.status
= tweet.status
When creating a tweet prototype you set the fields you want to the js-template replacement syntax, you could definitely dry this up, but I'm including it here in full for example purposes.
# tweet.rb
def self.prototype
Tweet.new{:username => "${tweet.username}", :status => "${tweet.status}"}
end
On the client you'd do something like:
var template = new Template($('#tweet-prototype').html());
template.evaluate(.. your tweet json..);
The last part will be dependent on how you're doing your templating, but it'd be something like that.
As previously stated, I haven't tried this technique, and it's not going to let you do stuff like loops or conditional formatting directly in the template, but you can get around that with some creativity I'm sure.
This isn't that far off what you're looking to do using Isotope, and in a lot of ways is inferior, but it's definitely a simpler solution. Personally I like haml, and try to write as much of my mark up in that as possible, so this would be a better solution for me personally.
I hope this helps!
To be able to share the template between the javascript and rails with a mustache template there is smt_rails: https://github.com/railsware/smt_rails ("Shared mustache templates for rails 3") and also Poirot: https://github.com/olivernn/poirot.

Categories