I have a controller with multiple actions/views, only one of which will take advantage of the ActionController::Live module. However, once included, AJAX actions (on unrelated pages) no longer render on the client at all.
The following code works without a problem:
my_controller.rb:
class MyController < ApplicationController
def index
// renders vanilla HTML/JS from index.html.erb
end
def update_index
// renders JavaScript from index.js.erb
end
end
index.html.erb:
<%= button_to({ controller: :my_controller, action: :update_index},
remote: true,
method: 'post') do %>
Update the text
<% end %>
<div id='content'>Some content</div>
update_index.js.erb:
$('#content').html('You clicked the button.');
The Problem:
As soon as I add include ActionController::Live to the controller, before even creating any JavaScript or Rails handles for Server Side Events (which work great), my existing code stops working. The following occurs:
Server sees the incoming AJAX request
All appropriate Controller functions are called
The JavaScript is not executed on the client side.
If you've included ActionController::Live in your controller, it seems to change the default header situation on returns to the client. Adding the following line to my non-SSE actions seemed to solve the problem:
response.headers["Content-Type"] = 'text/javascript'
But would love to hear if there's a better solution, or if I should just do this on all appropriate actions.
Related
I have a model called Question, and it has action create;
My goal is to display a flash message instantly, using a helper method (show_alert for example) when the instance is not valid.
question_controller.rb
def create
question = Question.new(question_params)
if question.save then
redirect_to show_question_path(question.id)
else
show_alert(:warning, question.errors)
end
end
application_controller.rb
helper_method :show_alert
def show_alert(type, message)
#type = type; #msg = message
respond_to do |format|
format.js { render :template => 'alert.js.erb'}
end
end
alert.js.erb
var div = $('<div></div>').addClass(`alert alert-${#type}`)
$('<ul></ul>').append( $('<li></li>').html(#msg)
div.append(ul)
$('#alerts').html(div)
But instead of displaying the flash, I get only the partial's code on the white screen.
see the screenshot
Since I've used respond_to I got another error: ActionController::UnknownFormat
I need the snippet of code in alert.js.erb to be executed, in order to render the flash, I think the trick is somewhere in the render function, but two hours of googling were just a waste of time.
Please help! Thank you in advance
ActionController::UnknownFormat error is showing up because the browser is sending HTML request to Rails server, but the respond_to block has only specified what to do in case of a javascript request from web server.
You will need to add a little bit of Ajax to achieve what you want. See this tutorial on Ajax. https://www.tutorialspoint.com/ruby-on-rails/rails-and-ajax.htm
Ajax will send a js request to browser in the background (i.e the browser will not refresh or show any signs of loading). This js request will be sent to Rails server and it will return the .js.erb file containing the script back to the browser. Now since this script was returned as a response to Ajax request by browser, the browser will already know that it is javascript that needs to be executed.
If you do not wish to implement Ajax, you have the alternate of doing something like this in your create controller:-
def create
question = Question.new(question_params)
if question.save then
redirect_to show_question_path(question.id)
else
redirect_to new_question_path(error: question.errors) #new_question_path is the action that displays the question form to the user
end
end
and then you can initialize an error variable in the action that displays the question form. e.g.
def new
#error=params[:error]
#rest of the code...
end
And then in somewhere in your new.html.erb (or whatever the html.erb file name is)
<script>
<% if #error %>
var div = $('<div></div>').addClass(`alert alert-<%= #type %>`)
$('<ul></ul>').append( $('<li></li>').html(<%= #msg %>)
div.append(ul)
$('#alerts').html(div)
<% end %>
// you might need to tweak the variable names in controller or the above code
</script>
(This code above may not be perfect. its just to give u an idea)
However this approach will not be as quick and beautiful as ajax because when the user will submit their question, the entire page will load again to display the error warning.
By default, all output from helpers is escaped. To show the HTMl as-is, you need to use the html_safe method (https://apidock.com/rails/v4.2.1/String/html_safe). See Using helpers in a view escapes the html?
I cannot be sure this without seeing your alert.js.erb but it could be that you need to use escape_javascript in your alert.js.erb
Something like (and I haven't tested this out) in your alert.js.erb
$('<%= escape_javascript("#{type} - #{msg}") %>').appendTo("#alert")
You can read more about it on Rails Guides - Working With Javascript in Rails
Hope this helps!
First let me tell you that I've searched for this and didn't manage to find any answer for my problem.
I have a lot of Javascript on my layouts/application.html.haml file:
### at the end of application.html.haml file
- if #instance_variable.condition
:javascript
// a lot js/jQuery code
:javascript
// more js code that uses #{ #instance.vars }
- if #another_condition.using.ruby.cody
:javascript
// more js code that uses #{ #instance.vars }
And I'm using instance vars within this code, which means that this vars will only be declared on the controller (application controller). As this is the application layout, these scripts run on every page of my website (and that's what I need of course).
What I want is to move all this code to a js.erb file (or .haml if possible). First - because is easier to manage this code in a separate file; Second - because I don't want to have <script> // a lot of js code </script> tags at the end of every html file, I want to have a <script async src='link'/> tag only.
Also I'm including already the application.coffee at the beginning of the file, but I need this script tag at the end of the html files.
I wouldn't recommend using partials for this. Because, your code uses variables, this means it changes depending on your variables. If you put them into a separate javascript file, the browser wouldn't know about the changes and use a cached file. A workaround would be to add some string (that changes when vars change) at the end of your filename, but then, you would lose all the benefits of moving your javascript into separate files.
A better way would be to define variables in your application.html.haml, move out your javascript code into separate files and just use defined variables.
application.html.haml
- if #instance_variable.condition
%script(src="path/to/my/script.js")
:javascript
= var some_var = #{#instance.vars}
%script(src="path/to/my/second_script_that_uses_var.js")
Thank you for your answer Uzbekjon, but after some research I found a way to do exactly what I wanted :)
In my 'layouts/application.html.haml' file, I added a script tag:
### at the end of application.html.haml file
%script{async: 'async', src: application_scripts_path}
Then I added this path to routes:
get 'application_scripts' => 'controller#application_scripts', as: :application_scripts
Then I just had to set this action application_scriptson my controller and create a new view (app/views/controller/application_scrips.js.erb):
class Controller < ActionController::Base
# 1
protect_from_forgery except: :application_scripts
def application_scripts
# 2
if condition.that.tells.me.this.request.is.valid
# 3
render formats: [:js] and return
end
render plain: ''
end
These steps were of course the harder ones to find out:
1 - I had to disable this protection, or else I would get this error:
ActionController::InvalidCrossOriginRequest at /application_scripts
Security warning: an embedded tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
2 - To make sure that no other site can request this script file (not that it could be a problem for me, but I preferred this way) I added a condition that makes sure the request comes from my website. In this case I was just checking if a user as logged in.
3 - The formats: [:js] tells Rails that the view is not .html, instead it's a .js file 'application_scripts.js.erb'
Finally I just had to move all my code from the application.html.haml file to the view file 'application_scripts.js.erb' and convert from haml code to erb as well.
<% #instance_variable.condition %>
// a lot js/jQuery code
// more js code that uses <%= #instance.vars %>
<% #another_condition.using.ruby.cody %>
// more js code that uses <%= #instance.vars %>
<% end %>
<% end %>
I am trying to establish a dynamic form on a contact's page. I would like on this page to be a link that says "add an email address" and then when I click on it, a form appears to enter email address.
So I used a link_to with remote true :
= link_to "Add an email", add_email_path, id:'link-remote-link', remote: true
In my controller i specified :
def add_email
render layout: false
end
But when I receive my response with listening on ajax:sucess, layout is still their in the variable. But I just want the form add_email.html.haml
In order to try to know if the code in my controller was executed, I tryed to put a creation of an object in it. Fact is that it was never created.
Never the less, rails console writes "Processing by ContactsController#add_email as JS
"
So...why is it not executed ?
Thank you :)
Layout
We set the layout in the application_controller to manage the ajax responses:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
layout Proc.new { |controller| !controller.request.xhr? }
end
You may wish to try this - to see if it's the call in your controller which is rendering the layout.
Personally, I think your layout: false call is being overridden with some other element / part of your controller. I'd recommend checking to make sure this is the case
--
Controller
As you've stated, the case may be that your controller isn't being called, or processed correctly.
This could be caused by a number of issues, most notably from having incorrect routes, or some other dependency preventing the method from firing properly.
To clarify, I would make sure I have the following set up:
#config/routes.rb
resources :contacts do
get :add_email, on: :collection
end
#app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def add_email
...
end
end
I have this Javascript view in my Rails 3 project:
app/views/expenses/new_daily.js.erb
var i = parseInt($('#daily').attr('data-num')) + 1;
//$('#daily').append('agrego fila ' + i + ' <br />');
$('#daily').append('<%= escape_javascript(render(partial: 'new_expense', locals: { i: i })) %>');
$('#daily').attr('data-num', i);
I want to pass my 'i' javascript variable to a ruby partial through locals, How I can accomplish this?
As far as i know there is no way to do it directly and the reason is fairly simple too, html is executed at the server side and javascript is a client side language which means its executed in your local browser, thats why if you even try to pass a variable between the two you'll have to make a request to the server,
However this problem is tackled by calling an AJAX request, this AJAX request does the same thing as sending a new request to the server however it does that without refreshing or reloading the page to it gives the users the illusion that no request was made.
a guy asks a similar question Here
and you can learn more about AJAX Here on MDN:
Yes you can pass the value by using jquery;
<%=f.text_field :email ,:id=>"email_field" %>
<script type="text/javascript">
var my_email= "my#email.com"
$(document).ready(function(){
$("#email_field").val(my_email);
});
</script>
Simple answer is you can't. Partials are expanded at server side, and JavaScript variables are set later at client side. You could make i (as a variable name) a parameter of the partial and use it there.
render :partial => 'xx', :locals => { :variable => 'i' }
And in partial
alert(<%= variable %>);
Check out the gon gem. https://github.com/gazay/gon
It gives you a simple object you can pass variables to that will be available to your scripts via window.gon
Also referenced here
http://railscasts.com/episodes/324-passing-data-to-javascript
1) You may create a js tag with global variable in you erb template, after that you will be able to access that variable from any js file
<%= javascript_tag do %>
window.productsURL = '<%= j products_url %>';
<% end %>
2) You can pass data to data-attribute in erb template and access it by js on client side that way $('#products').data('products')
<%= content_tag "div", id: "products", data: {products: Product.limit(10)} do %>
Loading products...
<% end %>
3) You can use gon, to use your Rails variables in your js
There is a good article, read it and fine solution for your specific case
http://railscasts.com/episodes/324-passing-data-to-javascript,
more comments are here http://railscasts.com/episodes/324-passing-data-to-javascript?view=asciicast
Here's a few different options on how to do it:
http://jing.io/t/pass-javascript-variables-to-rails-controller.html
The best other answers here are right that this can't be done by passing the javascript variable into an erb partial, since it is rendered on the server, not the client.
But since anyone looking for this is probably interested in a work-around solution, which I don't see here, I will post this example that works well with Rails UJS and Turbolinks.
First, you set up your controller to return a partial as HTML:
format.html { render partial: "new_expense" }
Next, write a javascript AJAX function in app/views/expenses/new_daily.js.erb:
var i = parseInt($('#daily').attr('data-num')) + 1;
$.ajax({
url: '/daily',
type: 'GET',
dataType: 'html',
contentType: "application/html",
success: function(response) {
$('#daily').replaceWith(response)
$('#daily').attr('data-num', i);
}
});
This is going to get your Rails partial as an html fragment that you can use to replace that part of your rendered page. You can use jQuery to get your data-num attribute value, do some math on it, replace the partial in your view, and then set the attribute value again.
You may ask why go to all the trouble of getting the Rails partial and replace it on the page, instead of just getting the data attribute, doing math on it, and setting that? The answer is that this is the best, and perhaps the only way of doing something which is really essential when rendering a Rails partial using UJS while handling an asynchronous response to an action.
If you are handling an asynchronous response from your server in a create.js.erb template, then your variables (#daily, for example) are not going to reflect the work done after the request has completed (for example, if there has been processing on a background server like Sidekiq). In that case you don't have up-to-date action response variables to pass into your Rails partial in the js.erb file, but you also can't pass the javascript data response into your partial, as pointed out in this question.
As far as I know, this approach is the only way to get a fully up-to-date partial after receiving a response to an asynchronous response (not shown). This get you the up-to-date partial, allows you to get your javascript into it, and is flexible enough to work in pretty much any use case.
Let's make shure we understand each other. Your erb template (new_daily.js.erb) will be processed on the server side, ruby code will be evaluated (within <% %>), substitution made, and then resulting javascript will be sent to browser. On the client side the browser will then evaluate this javascript code and variable i will be assigned a value.
Now when do you want to pass this variable and to what partial?
Lately i have run into a few applications that are using js.erb and i am not really sure how to use it ...here is the code below. Can someone help me understand how this works?
in the routes.rb file
map.resources :player_emails
my controller player_emails_controller.rb in the create action
def create
#player_email = PlayerEmail.create(params[:player_email])
if #player_email.save
#response_txt = "The player has been emailed."
PlayerEmailsMailer.deliver_pattern_email(#something, #player_email, request.host_with_port)
#error = false
else
#error = true
#response_txt = "Please make sure you entered your name and a valid email address."
end
end
then i have the file player_emails/create.js.erb
$('#player_email_ind').hide();
$('#player_email_submit').show();
$('#player_response_msg').html("<%= escape_javascript #response_txt %>").fadeIn();
<% unless #error %>
$('#player_email_form')[0].reset();
<% end %>
i know what the jquery is going but i dont know how this is doing the ajax call. Does it just automatically do an ajax call when there is a js.erb...can someone explain the way this works and why i dont need a respond_to in the controller action telling it this is format.js
If a js (ajax) request is made it will respond by rendering the js.erb file and viceversa.
This is the default behaviour that is being performed:
respond_to do |format|
format.js{
render :template => 'create.js.erb'
}
format.html{
render :template => 'create.html.erb'
}
end
When the form is submitted, it does a POST to /player_emails. The resource declaration in routes.rb ensures the request is handled by PlayerEmailsController#create.
The controller is responsible for handling each format it receives. In the case of an AJAX call, the format is 'js', and is set by explicitly adding the format string to the end of the URL (/player_emails.js) or (more likely) by deducing the format from the request header.
In your case, the create action does not expect anything other than AJAX, so it takes a shortcut and omits the respond_to and format blocks. The controller has already figured out that the format is 'js', so when create is complete it takes the default action of rendering the appropriate template for the format (create.js.erb).
Does your form submit button have a :remote => true on it? If so, there might be some JavaScript in rails.js or application.js that automatically submits via AJAX. Bottom line is, there has to be some JavaScript somewhere that is making an AJAX call and asking for a js or JSON response, otherwise it would be an html request.
As for why you don't need a respond_to block, I'm not entirely sure. Maybe since the call is always being made by AJAX and there is a js.erb template available, it just does its thing without complaining. Is there an html.erb template at all? If not, try doing a regular form submit and see if it complains.