js.erb is rendering text not JS - javascript

Having a strange issue trying to update search results via AJAX in rails in that rails is loading the js.erb in a <pre> tag at raw text on the page and not firing any js in that file.
in index controller
if request.format.js?
respond_to do |format|
format.js { render layout: false }
end
end
Form set to fire AJAX with remote: true
= form_tag(search_path, id: "form", remote: true, :'data-type' => 'script') do
index.je.erb
alert('hello');
$("#results").innerHTML("<%= escape_javascript(render partial: 'results', locals: { results: #results } ) %>");
routes.rb
match 'search', to: 'properties#index', via: :post, defaults: { format: 'js' }
In routes I have tried to force js from the form as previously it was JSON, but makes no difference.
From the logs, the controller is receiving the js request but i get this in CHrome debugger:
(index):69 Resource interpreted as Document but transferred with MIME type text/javascript: "http://localhost:3000/search".
Sticking a alert("Js is working") works without issue so JS is there and firing
Im stuck, this should be working by now, is it something silly i have missed, or something new in Rails 5 i am not aware of?
If i can provide any more info to help you halp me please just ask!
Thanks in advance

OK then. ´remote: true` is not enough it seems! It does seem to submit the ajax request to the controller, but the controller is determined that it is text.
adding
jQuery ->
$('#form').on "change", ->
$.ajax '/properties/index',
type: 'GET'
dataType: 'script'
data: $('form').serialize()
and hyjacking the form submit into pure ajax seemed to fix it all!

Related

Respond with *.js.erb using nonce strategy for CSP

I'm implementing a CSP using rails 5.2.1 content security policy DSL. I've got my policy set to something like:
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.connect_src :self
#...
policy.script_src :self
end
# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
I also have <%= csp_meta_tag %> in my application.html.erb
At this point I need to add a nonce: true flag to any inline scripts for these to satisfy the policy. I've done this and it works as expected. However, I'm having trouble maintaining existing AJAX style functionality. For example, I have something like (note the remote: true):
# index.html.erb
<%= link_to create_object_path, id: "#{object.code}",method: :post, remote: true do %>
<button type="button">Create object</button>
<% end %>
In my controller
def create
#object = current_user.object.create
respond_to do |format|
if #object
format.js
else
redirect_back
format.html
end
end
end
In my *.js.erb file
$("#<%= #object.service.id %>").text("Added!");
The object is successfully created but I believe the policy is blocking the above "Added" success message that I add to the DOM. I have not seen any errors in the console so I'm not sure where to go from here.
My understanding in this scenario is script tags are temporarily inserted with the contents of the *.js.erb file and these script tags do not contain the nonce. Or, it is a mismatch.
I've been stuck on how to troubleshoot from here. Any guidance here is much appreciated even if different architectural pattern for sending data to client is the way forward. Thanks in advance.
I ran into a similar issue. In my case, it didn't refuse to run the js.erb file itself but rather scripts in templates nested within that file through the use of render. So, this answer may have limited utility to your specific case. That said, I did try to reproduce your issue using Rails version 6.1.1 and couldn't.
However, even if you get past the initial hurdle of getting just your .js.erb file to run, you can still run into the issue of nested scripts: if your .js.erb file renders a template that contains a script tag. That script won't run because the request from which it originated assigns it a new nonce, which won't match the nonce in the meta tag.
So, to those coming here from a search engine as I did, here's the general strategy I pursue to get async embedded JS working with CSP for that nested case and assuming the .js.erb file itself runs. Using your case as an example:
Send the nonce along in the AJAX request. I suppose you won't get around writing some custom JS to send the request. Something like:
document.getElementById('<%= object.code %>').addEventListener('click', e => {
e.preventDefault(); // So we don't send two requests
fetch('<%= create_object_path %>', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({
nonce: document.getElementsByName('csp-nonce')[0].content
})
});
});
This sends the nonce from the meta tag to the server in the form of a nonce parameter.
You may need to remove remote: true from your link for this to work. And of course, this script will itself need to be 'nonced' or else it won't run!
Assign the nonce to a #nonce instance variable in the controller:
#nonce = params[:nonce]
Wherever you render scripts, do:
<%= javascript_tag nonce: #nonce || true do %>
...
For those wondering how to get the same to work with their existing asynchronous forms:
Add this form field: <%= hidden_field_tag :nonce %>
On form submit, assign the nonce from the meta tag to the hidden field:
document.getElementById('id_of_submit_button').addEventListener('click', async e => {
document.getElementById('nonce').value = document.getElementsByName('csp-nonce')[0].content;
});
In this case, you don't want to prevent the default behavior on the event because you want the form to submit.
Then continue with step 2 above (assigning the nonce to a controller instance variable).
I hope as a general strategy this is useful to some. And I hope it can serve as inspiration for how to get the .js.erb file itself to run.
UPDATE: Of course, for your specific (but limited) use case, you could simply return the object's service id as part of some JSON object you return to the client instead of rendering a .js.erb template. I say "limited" because this won't work for people who really need to render templates.
If you did want to render your .js.erb file, I suspect something like this could work for your case as well, where instead of checking whether the HTTP_TURBOLINKS_REFERRER header is present, you check for request.xhr?. Just know that starting in newer Rails versions, remote: true doesn't set the requisite header for request.xhr? to work anymore. But since you're on 5.2.1, it may work for you.

Updating instance variables through AJAX call in Rails to display in form

Here's what I'm trying to do:
The user pastes a URL.
The input box that the user pastes in has an :onpaste that triggers urlPasted() function.
urlPasted() function submits the form that input box is in, which does an AJAX call to a custom function named lookup_profile.
In the controller, lookup_profile function does some web requests, and then updates some instance variables.
Once those variables are updated (takes ~5 seconds), the view has a function that waits 20 seconds and updates textboxes on the modal with the results of those instance variables.
Here's what I have thus far in the view:
<%= form_tag url_for(:controller => 'users', :action => 'lookup_profile'), id: "profileLookupForm", :method => 'post', :remote => true, :authenticity_token => true do %>
<div class="form-group row">
<div class="col-sm-12">
<%= text_field_tag "paste_data", nil, onpaste: "profileURLPasted();", class: "form-control"%>
</div>
</div>
<% end %>
<script type="text/javascript">
function profileURLPasted() {
// Once the user pastes data, this is going to submit a POST request to the controller.
setTimeout(function () {
document.getElementById("profileLookupForm").submit();
}, 100);
setTimeout(function () {
prefillForm();
}, 20000);
};
function prefillForm() {
// Replace company details.
$('#companyNameTextBox').val("<%= #company_name %>");
};
</script>
Here's what the controller looks like:
def lookup_profile
# bunch of code here
#company_name = "Random"
end
Now here's the problem I have. When the user pastes the data, it submits perfectly to the custom_action lookupProfile. However, after lookupProfile runs its code, rails doesn't know what to do afterwards. By that, I mean it gives me this error:
Users#lookup_profile is missing a template for this request format and
variant. request.formats: ["text/html"] request.variant: []
When in fact, I actually have a file at views/users/lookup_profile.js.erb. For some reason, it's trying to render the HTML version. I don't know why.
Secondly, I've tried putting this in the controller towards the end:
respond_to do |format|
format.js { render 'users/lookup_profile'}
end
but that results in this error:
ActionController::UnknownFormat
Any help would be greatly appreciated. I just want the custom function to run, update the instance variables, and let me update the current form with that data.
Here's another stackoverflow reference of something similar I'm trying to do: Rails submitting a form through ajax and updating the view but this method doesn't work (getting the actioncontroller error)
* EDIT 1 *
Ok, so I fixed the ActionController error by replacing my form_tag with:
<%= form_tag(lookup_profile_users_path(format: :js), method: :post, :authenticity_token => true, id: 'profileLookupForm', remote: true) do %>
But now it's actually rendering the actual javascript into the view, and I don't want that. I simply want to be able to access the instance variables that were updated in the lookup_profile action, not display the view.
* EDIT 2 *
So I think my problem comes down to this: Placing a button in the form and submitting from IT is different than my javascript code that submits the form. If I can figure out what's up with that, then I think I may be in good shape.
You are mixing a few things there. First of all, instead of doing document.getElementById("profileLookupForm").submit() you should do an ajax request, I guess the submit() method ignores the remote: true directive from rails.
So, change the submission to:
form = getElementById("profileLookupForm");
$.post(form.action, {paste_data: this.value}, 'script')
// form.action is the url, `this` is the input field, 'script' tells rails it should render a js script
That way the request is done async and the response does not replace the current page.
Now, what I think you are mixing is that #company_name won't change with that ajax request. When you render the form and everything else, #company_name is replaced with the actual value IN THAT MOMENT and will not change after your post request since the reference is lost. So this line:
$('#companyNameTextBox').val("<%= #company_name %>");
will be
$('#companyNameTextBox').val("");
al the time.
What you want is to respond with a script that updates the field with the value that you set to #company_name (also, waiting arbitrarilly X seconds is a really bad practice).
So, instead of responding with:
format.js { render 'users/lookup_profile'}
create a view lookup_profile.js with the code that you want to execute
$('#companyNameTextBox').val("<%= #company_name %>");
here, #company_name will actually be the value obtained with those requests you told before, the script is generated at the moment and excecuted as a response of the request.

Rails and AJAX remote: true what else is required?

I'm confused about remote:true in Rails forms, I thought some Javascript was required to make it asynchronous but this just seems to break my page.
Here is a really simple index.html.haml that includes a partial to show all appointments:
%h1 Calander
%h2 AppointmentsController
%h3 Make a new appointment
= form_for #appointment, remote: true do |f|
= f.text_field :title
= f.text_field :appt_time
= f.submit 'Make appointment'
#appointments
=render 'appointments'
Here is the previously mentioned partial:
-#appointments.each do |a|
%h3= a.title
%p= a.appt_time
Controller methods for index and create:
def index
#appointments = Appointment.order('appt_time ASC')
#appointment = Appointment.new
end
def create
#appointmet = Appointment.create(appointment_params)
redirect_to :root
end
Now this works fine. I can add a new appointment, hit submit and the new appointment shows up without the page refreshing, I think because I have included remote: true. So do I need to add anything else to handle the request? Am I violating best practices by not including something to handle this request and relying entirely on remote: true?
Nothing more required unless you want some callback after ajax call. You did not break any conventions. You can read this document to get ride of confusion.
Let's take a step back.
Web applications can respond to different request formats. Rails has built-in format handling.
So a request might ask for index via HTML, which response with an HTML file. It might also request index via JSON, XML, PDF or even JavaScript.
Whenever you add remote: true you are telling your form make a POST request via JS instead of HTML.
In your views you will have a bunch of HTML.ERB files. These views are request responses.
So to handle a JS request to index, you will need a app/views/appointements/index.js file.
This will be sent as the response to the request and the browser will know what to do with a JS response.
In index.js you can write JS that will be executed once the response is received.
You can also load partials into the page.
For example:
# app/views/appointements/index.js
$('#appointements').html('<%= j render "appointements" %>')
Which will render the partial content as a JavaScript string for the response.
http://guides.rubyonrails.org/working_with_javascript_in_rails.html

remote: true option is not working with file field in rails 4

I am trying to submit form via ajax using remote:true option which contains input fields as well as file fields (need to upload images)
Here is my view
= form_tag("/campaigns/upload_form", method: 'post', format: :js, enctype: 'multipart/form-data', remote: true) do
which contains file field
= file_field :campaign, :fb_photo, class: 'form-control', id: 'fb_campaign_product_photo', title: 'Upload Image for your Product'
In controller action
def upload_form
# do some stuff
respond_to do |format|
format.html
format.js
end
end
When I submit form without file field it is processing and rendering as JS correctly
Started POST "/campaigns/upload_form" for 127.0.0.1 at 2016-03-21 18:06:51 +0530
Processing by CampaignsController#upload_form as JS
but when I am trying to submit file fields, it is processing as HTML and throwing template missing error as I haven't added view for that.
Editing this post to tell that:
I have tried remotipart gem as suggested in below answer and it worked for me but still getting following error in browser console
jquery.js?body=1:9665 POST http://localhost:3000/campaigns/upload_form 500 (Internal Server Error)
and getting following error in development.log
ActionView::MissingTemplate - Missing template campaigns/upload_form, application/upload_form with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml, :jbuilder]}. Searched in:
* "/home/tudip/Documents/cpx/app/views"
* "/home/tudip/.rvm/gems/ruby-2.2.1/gems/twitter-bootstrap-rails-3.2.0/app/views"
* "/home/tudip/.rvm/gems/ruby-2.2.1/bundler/gems/devise-9568e28d663e/app/views"
Thanks in advance
remote: true does not work with multipart/file upload.
You can use remotipart gem for this.
https://github.com/JangoSteve/remotipart/blob/master/README.rdoc
You're using form_tag instead of form_for. So, use file_field_tag instead of file_field.

j(render(#partial)) returns error: ActionController::UnknownFormat

I'm trying to render a partial with ajax, but for some reason it returns this error:
ActionController::UnknownFormat in ThingsController#upvoterandom
ActionController::UnknownFormat
I'm very confused because I accomplished something with an essentially identical format before, and I never had any problems with it. Does anyone see anything wrong with my code? I can render a string with the ajax; it's only when I try to render a partial that I get the error. By the way, I achieved this error by deleting the format.html line and then visiting the upvoterandom_thing path directly in my browser.
views/things/show.html.erb
<div id= "randomajax" >
<div id="randajax">
<%= link_to #rand.name, thing_path(#rand) %>
<%= link_to image_tag("UpArrowGray.jpg", class: "rand_up_vote"), remote: true, %>
<script type="text/javascript">
function reload_script() {
$(".rand_up_vote").click(function () {
$.get( "<%= upvoterandom_thing_path(:id => #rand.id) %>", function( data ) {
});
});
}
reload_script();
</script>
</div>
</div>
controllers/things_controller.rb I put asterisks around the line highlighted with the error.
def upvoterandom
#thing = Thing.find(params[:id])
#...
***respond_to do |format|***
format.html { redirect_to root_path }
format.js
end
end
views/things/upvoterandom.js.erb: .html("test") returns "test", so the problem has to be in the rendering.
$('#randomajax').html("<%= j(render(#randajax)) %>");
views/things/_randajax.html.erb
TEST
THIS IS THE OTHER NEAR-IDENTICAL AJAX STRUCTURE THAT WORKS:
views/things/show.html.erb
<%= form_for([#thing, #comment], remote: true) do |f| %>
<%= f.text_area :text %>
<%= f.submit "Post", id: "postacomment" %>
<% end %>
controllers/comments_controller.rb
def create
#thing = Thing.find(params[:thing_id])
#comment = #thing.comments.create(comment_params)
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
views/comments/create.js.erb
$('#comments_h2').prepend("<%= j(render(#comment)) %>");
views/comments/_comment.html.erb
TEST
I'll apologize up front for the long answer. I've tried reproducing your issue in multiple ways, but I think you might be looking in the wrong direction. I've included the entire story so you can see if it all matches up, and if it doesn't it hopefully leads to insights which will help you resolve the issue.
ActionController::UnknownFormat happens before view rendering
If Rails presents you with the ActionController::UnknownFormat error, it means that your controller does not respond to the format that is being requested. The error is raised by the respond_to method, at the exact line you highlighted.
Had an error been raised during view rendering, then it would have been bubbled up through either the line with format.html or format.js. So this error is certainly not caused in the view rendering part.
Reproducing the ActionController::UnknownFormat error
The only way I can get the exact error message you posted, is if I request the AJAX response page directly in the browser. Then you see the Rails error page, which will also show you which format the request was done with. You're probably requesting the page with a format other than html or js, in which case the ActionController::UnknownFormat error is triggered, since your controller only responds to the html or js format.
I think the actual issue is hiding somewhere else.
Reproducing from views/things/show.html.erb
I have tried to reproduce the error from the views/things/show.html.erb page as well. When doing this with your original code, I get a syntax error on the following line:
<%= link_to image_tag("UpArrowGray.jpg", class: "rand_up_vote"), remote: true, %>
This happens due to the comma after remote: true. Because of this, I assume you haven't been constantly testing with the remote link. When testing it without the remote: true, the reload_script function triggers a jQuery AJAX request. You're doing this with the following line:
$.get( "<%= upvoterandom_thing_path(:id => #rand.id) %>", function( data ) {
});
This actually triggers an XHR request with the format */*. It basically tells Rails that any format will do. Here's what happens:
The ThingsController responds with the first format you've defined in the respond_to block, which is the HTML format. This triggers a redirect to your root path.
jQuery follows the redirect, again using the */* format.
The controller at your root path responds with the first defined format, or HTML by default if no respond_to block is present.
jQuery then loads that response.
There's no way that the ActionController::UnknownFormat could have been raised from ThingsController when reproducing it like this.
What I think
This is mostly guessing work, so please correct me if I'm wrong:
You clicked the upvote remote link and nothing visibly happened.
You added a JavaScript to explictly fetch the page through AJAX, but still nothing visibly happened.
You visited the things/1/upvoterandom.js (or alike) page directly with your browser. In this case an ActionController::InvalidCrossOriginRequest would have been raised.
You visited the things/1/upvoterandom.json (I used JSON, but it could be any format other than HTML or JS) page directly with your browser, and you got the ActionController::UnknownFormat error.
If this is how it happened, you need to go back to step 1, and start reproducing the issue in another direction:
Go to the things/1 (or another Thing ID) page.
Open up the Developer Tools/Web Inspector of your browser.
Go to the view which shows the network communication (in Chrome this is the tab called Network).
Now click the link, and see what communication happens between your browser and your Rails application. If a lot happens, you often can filter for XHR only, which only show AJAX requests.
If you see a 500 status popping up there, you need to check the Rails server output for an error with stacktrace.
I can only guess what the actual cause of the issue is, but I think it might actually be some error when rendering the view, As you said, the only case where it doesn't work is if you render the partial. I hope this information helps you to resolve the issue.
In views/things/upvoterandom.js.erb you're trying to render #randajax like so:
$('#randomajax').html("<%= j(render(#randajax)) %>");
But I don't see #randajax being assigned anywhere, which means that it's nil.
Trying to render nil is the cause of this issue. But do confirm this by doing the following the first:
$('#randomajax').html("<%= j(render(nil)) %>");
If it returns the same error, then we've found the culprit.
Either assign #randajax something prior to rendering it or simply use:
$('#randomajax').html("<%= j(render(path_to_partial)) %>");
Well I haven't figured out why the syntax I was trying wasn't working, but this syntax does work:
$('#randomajax').html("<%= render 'randajax' %>");

Categories

Resources