Dojo request.post to Rails Fails - javascript

I'm converting a website from Symfony to Ruby on Rails and am finally down to my javascript pop up email contact form. I don't want to rewrite the form or the java script as it took a lot of work to get it to work in the first place. That will be phase two.
Here's where I'm having the problem:
sendBtn = new Button({
label: "Send",
onClick: function(){
if (emForm.validate() == true){
// Post the data to the server
request.post("/contact/create",
{data: domForm.toObject("contact-form"),
// Wait 2 seconds for a response
timeout: 2000
}).then(function(response){
emailDialog.hide();
alertDialog.set("content",response)
alertDialog.show();
});
}
}
},"submit-btn");
I know it gets to the request.post as I copied the line "email.Dialog.hide()" just before it and it hid it. I later added code to catch any errors. It goes there immediately and not after the two second timeout. I'm using dojo here, by the way. So I suspect it doesn't like the "/contact/create" as it's the only thing I changed.
In my routes.rb I have:
get 'contact/create'
Do I have the right information in my post? If not how do I get there? The javascript is included in application.html.erb so it can be invoke from all pages on the site.
In case it's pertinent, my contact_controller.rb is currently just this:
class ContactController < ApplicationController
def create
respond_to do |format|
#format.html {}
#format.js {}
format.json { render :json => {:response => 'Amazing, it works'} }
end
end
def show
end
end

Take a look at your network tab in dev tools, it should tell you why it is failing post... I'd say try adding handleAs: 'json' option to your request.post. For more on dojo/request, read this

As it turned out, I had the right path in the request.post statement. I found out my copying the Javascript into my html.erb file so I could use
<%= contact_create_path %>
in it's place. I ended up getting the same value so that wasn't the problem. I then checked my Firebug console. Rails sends a nice dump of the problem. I was getting a 404 error. The problem was that I was doing a post and there was no route for it. So I changed the routes.rb file from
get 'contact/create'
to
post 'contact/create'
This might cause me other problems later on if I want to do a non-Javascript version.
I then got another error:
ActionController::InvalidAuthenticityToken in ContactController#create
Through the help of Stackoverflow I found the fix. I added the second line below:
class ContactController < ApplicationController
skip_before_filter :verify_authenticity_token
...
Again, this solution may cause other problems. Skipping verification doesn't seem like a good thing. I had a number of other problems getting the whole process to work, but their specific to my application.

Related

Rails & AJAX, is there a reason you shouldn't render html view directly in controller action for ajax to process?

The classic way to work with Rails & Ajax is always something that looks like this:
// JS - let's assume this submits to dummies#create
$(form).submit()
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
respond_to do |format|
format.js
end
end
# /views/dummies/create.js.erb
$("page").append("<%= escape_javascript(render partial: 'dummy_view' ) %>");
# /views/dummies/_dummy_view.html
<h1><%= #dummy.name %></h1>
I've always been curious, because the above seems to create a random create.js.erb file with very little meat... is there a reason (e.g., it's terrible convention, or terribly insecure or whatever), why you should NOT instead just render the view directly back to ajax?
// JS - basically takes responsibilites of create.js and puts it into the always
$.ajax(...).always(function(xhr, status){
$("page").append($(xhr['responseText']))
// responseText contains the partial rendered by the controller action
})
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
render partial: 'dummy_view'
end
# /views/dummies/_dummy_view.html
# unchanged
<h1><%= #dummy.name %></h1>
NOTE above is pseudo-code, apologies for minor errors. The conceptual idea & question remain unchanged, though.
The create.js.erb is not random, is the view for the action with the expected format.
Generally, you may not have a view so simple (You may have different selectors other than "page", you may have some extra js code to be executed after or before the append), a js view/script is the general solution for an ajax request to give the response full control over what to do.
You could have what you want, but it will just work for your particular case when the selector is always "page" and you only want to append html to that element. Nothing prevents you from doing that (though you might want to use a custom ajax request and not rails' one since it sets js format by default and executes the response's script).
The convention is that a rails' remote request renders a js script, you can move out of the convention if you want. You'll lose a lot of flexibility with your approach as is (like... what if the create action fails an you need to display errors?).

Rails update element based on AJAX request?

I've been reading a lot about Rails and AJAX and 5.1 Unobtrusive javascript. It explains a lot about responding to Rails version of AJAX calls with a .js file for example.
However what im wanting to do isn't serving up an entire .js file, it's simply updating an element after a <% link_to %> POST request. From my understanding setting remote: true submits it as a AJAX request.
Essentially I have a "Post" which a user can like via a linked Like button. This sends a POST request to the "Post" controller which updates a post to liked and adds a like to the post.
Unfortunately to see the effects of the post being liked (Which is simply that the link changes color as well as the font-awesome icon) you need to refresh the page. I basically want it to update without needing refresh.
I "think" based off what i've read I need to make a respond do and respond via .js to the request with a .js file in the view I want to update (for instance if the controller action is called "like", maybe a like.js.erb file in the view im updating?). But I don't want to serve an entire new page..or would this simply just run the .js?
Then I could do something like $('i.fa-icon#id').style.color = "blue" or something? (Im assuming I can send data from the controller to the .js.erb file?). Not sure the best way to do this, don't rails elements a lot of times have some sort of data-attribute or something (Im still a beginner at this).
Your description is quite correct!
Opposed to the other answer, you don't even need a event listener but as you said you want to have a respond_to in the controller.
So starting from the html:
# post/index.html.erb
<div id="like-button">
<%= button_to "Like this post", post_path(#post), remote: true %>
</div>
Note, that when you use a button_to helper it'll be a POST request by default.
If you click it, it'll go to the controller#update, which you want to change to this:
#posts_controller.rb
...
def update
#post.save
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js # <-- will render `app/views/posts/update.js.erb`
end
end
Note: the format.html is rendered when JS is disabled.
Now in the scenario that JS is enabled, it executes the app/views/posts/update.js.erb file. It can look like this:
const likeButton = document.getElementById('like-button');
likeButton.innerHTML = '<%= j render "posts/liked-link", post: #post %>';
What is the last line doing? Of course, you can change the style directly with the JavaScript, but you can also render a new partial - and this you will create in a new html file:
# app/views/posts/liked_link.html.erb
<div id="like-button ">
<p>"You liked this post!" </p>
</div>
I just changed the link/button to ap now, but of course you can do whatever you want.
Hope that makes sense :)
Not sure if I understand the question, but if you want to update like button:
What you want to do is to add an event listener to the button, and when clicked it makes a POST request to whatever route handles the likes(with the correct parameters) and your controller should respond with the like object (or whatever in the database gets stored). Have your post request on success method to grab the like button and change it to whatever you want it to look like
$(“#like-btn”).click(function(){
Rails.ajax({
url: "/some/url/to/like/controller",
type: "post",
data: [your post data],
success: function(data) { $(`#${ data[“btn-name”] }`).attr(“color”, “blue”; }
})
}
You can stick this script right in the bottom of the html page
You don’t have to do it exactly like this, just giving you an idea of how to set up the pattern of having JavaScript and Ajax handle the post request and updating of the frontend instead of using html buttons

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.

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' %>");

How to improve speed execution of coffeescript in rails?

To increase speed of page loading I`ve implemented creation comments via AJAX. It is simple and not heavyweight.In controller action I have:
def create
#comment = #commentable.comments.new(params_comment)
respond_to do |format|
if #comment.save
flash.now[:notice] = "Your comment added."
#response = {comment: #comment, model: #commentable}
format.js { #response }
format.html { redirect_to #commentable }
else
format.js { render nothing: :true, status: :not_acceptable }
format.html { redirect_to #commentable, status: :not_acceptable }
end
end
end
and js file:
$("<%= escape_javascript( render 'comments/comment', #response)%>").appendTo(".comments").hide().fadeIn(500)
$('.notice-wrapper').html("<%= j(render partial: 'shared/notice') %>")
$('.alert').fadeOut(3000)
if $(".no-comments").css("display") is 'block'
$(".no-comments").hide()
$(".new_answer").hide()
$(".open").show()
But instead of speed up performance I got the opposite effect. Response time through JavaScript increased on 100-200 ms(~300ms total). Is this normal behavior or I am doing something wrong? Is there any way to improve speed little bit?
My performance test:
UPD:
My performance test with just JS file.
Let's put aside for the moment my opinion that embedding ERB in CoffeeScript is utterly gross to look at and unmaintainable. It is indisputable that there's a huge performance impact when you generate and compile CoffeeScript on every request.
You also lose any chance at HTTP caching.
My first suggestion would be to separate CoffeeScript from ERB. Populate hidden fields in your ERB with the data you need, and then mine those fields in your CoffeeScript.
But if you must embed ERB in what should be static files, embed the ERB tags in pure JavaScript instead, and let the compiled CoffeeScript use those.
The code you've written wouldn't really be expected to speed up the request, because Rails still need to process all the ERB etc. You're still returning rendered HTML and sending it to the browser where it is added to the DOM.
If you wanted to make it 'faster' you could simply render the #response as json and deal with it on the client using jQuery or a front end framework.
The code does make it nicer for the user, however, because it doesn't refresh the entire page.
My suggestion would be to hook into the form request, and on success, render the new comment, else re-render form with errors. An example:
# app/javascripts/comments.coffee
$('#comment-form').bind 'ajax:complete', (data, status, xhr) ->
// psuedo code
1. if status is success
append comment -- $('#comments').append(data.comment)
2. else
re-render form with errors -- $('#comment-form').html(data.form)
Return comment template (comments/comment) and append to comments
Update your controller to return the form with JS response if not_acceptable.
Note the file is found in app/javascripts/comments.coffee

Categories

Resources