0

This works fine:

class CommentsController < ApplicationController
  def destroy
    respond_to do |format|
      format.js { render "destory.js.erb" }
    end
  end
end

This does not work at all (the JavaScript never fires):

class CommentsController < ApplicationController
  def destroy
    respond_to do |format|
      format.js
    end
  end
end

Is that expected?

I thought Rails should automatically know to use the /comments/destroy.js.erb view? Do I always have to explicitly define the JavaScript view?

filmnut
  • 746
  • 1
  • 7
  • 16
  • That shouldn't be necessary. What do you mean by "doesn't work"? Does it produce an error? Does it show some other view? – tadman Aug 12 '19 at 17:44
  • @tadman: By "doesn't work" I mean that the JavaScript never fires (the view just contains a basic `alert()`). When I view the Rails logs, I can see that Rails is trying to render the HTML view (not the JS view):`Rendering comments/destroy.html.erb Rendered comments/destroy.html.erb (0.6ms)`. – filmnut Aug 12 '19 at 17:46
  • Returning JavaScript is a little unusual in terms of application design, it's usually preferable to return data like as JSON which client-side JavaScript can then act on, but this should work in principle. The problem might be that your client isn't explicitly asking for JavaScript, it's asking for HTML via the `Accepts` header. This is not the case with AJAX asking for JSON. – tadman Aug 12 '19 at 17:48
  • @tadman: Oh, interesting. Maybe I'm mistaken here. My goal is just to *execute* the JavaScript contained within `/comments/destroy.js.erb`, so that this JS modifies the user's page without a page re-load. Am I approaching this the wrong way? – filmnut Aug 12 '19 at 17:49
  • It's a valid approach, but not an ideal one. Rails has tried to steer away from *returning* JavaScript (deprecating RJS for example) and instead towards having all your application code in `application.js` and other included files. Ideally define an UJS action that handles this at the data level, like JSON exchange, instead of by returning actual JavaScript. You could return the data required for the redirect and do that client-side. – tadman Aug 12 '19 at 17:54
  • 1
    How are you going about this because if you want the js page to load you would have to request `comments/destroy.js` which is generally a delete request. `comments/destroy` will default to `html`. the route would be `DELETE comments/destroy(.format)` where `(.format)` is an optional format (defaults to `html`) you are requesting which is how `respond_to` determines how to respond – engineersmnky Aug 12 '19 at 17:55
  • @tadman: Gotcha. That's interesting. I'm just following the Rails docs about this topic here: https://guides.rubyonrails.org/working_with_javascript_in_rails.html#a-simple-example In that tutorial, it looks like the Rails Team is still advising responding with JavaScript, right? Or, am I misreading this? – filmnut Aug 12 '19 at 18:09
  • I think it's just demonstrating that you can, not necessarily that you should. – tadman Aug 12 '19 at 18:17
  • 1
    @tadman. Thanks! This is all super helpful to know. I Googled a bit, and I think I found a tutorial that explains what you're suggesting here: https://www.engineyard.com/blog/ajax-on-rails-with-unobtrusive-javascript Does that sound like the ideal approach? – filmnut Aug 12 '19 at 18:25
  • Yeah, that's what I'm talking about. That's what the Rails team is trying to encourage. – tadman Aug 12 '19 at 18:28

1 Answers1

0

Thanks to @tadman, I ended up approaching this problem from a completely different perspective, and my issue is now resolved.

As explained in this excellent Engine Yard tutorial, I moved away from the respond_to and format.js approach entirely. I'm now instead using the AJAX approach that apparently the Rails Core Team prefers -- that is:

My View:

<p data-js-comment-id=<%= c.id %> class="blank_links" style="margin-top:-12px;">
  <%= c.body %>
  <%= link_to blog_post_comment_path(@blog_post,c), remote: true, method: :delete, 
  data: { confirm: "Are you sure?" } do %>
    <%= fa_icon "trash" %>
  <% end %> 
</p>

My controller:

class CommentsController < ApplicationController
  def destroy    
    @blog_post = BlogPost.find(params[:blog_post_id])
    @comment = Comment.find(params[:id])
    @comment.delete
    render json: @comment
  end
end

My /app/assets/javascripts/blog_posts.js file (which I renamed from originally being /app/assets/javascripts/blog_posts.coffee because I don't use CoffeeScript):

$(document).on('turbolinks:load', function() {
  $("[data-js-comment-id]").on("ajax:success", function(event, data, status, xhr){
    var comment_id = xhr.responseJSON.id;
    $( "#comment-" + comment_id ).fadeOut(650);
    get_default_success_toastr("Comment deleted!", "And just like that, pffft. It's gone!");
  });
});

Note above the $(document).on('turbolinks:load', function() { line. That's very important, as noted here and here.

And finally, my application.js file:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require activestorage
//= require_tree .
//= require popper
//= require bootstrap-sprockets
//= require toastr

The reason that I included the application.js file above is because: (a) the order in which you require the libraries appears to make a difference, and (b) notice that I removed rails-ujs

Works perfect!

filmnut
  • 746
  • 1
  • 7
  • 16