1

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).

msmith1114
  • 2,717
  • 3
  • 33
  • 84

2 Answers2

4

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 :)

Clara
  • 2,677
  • 4
  • 16
  • 31
  • Wow never even knew about the `button_to`! learn something new everyday I guess. BTW what is with the "j" in the render link on the js file you posted? Sidenote: How would you render a new partial? I assume the same way as you would in rails. Just render whatever partial in the .js.erb file? – msmith1114 May 30 '19 at 03:49
  • So, the render renders the partial which is in `view/posts/liked_link.html.erb`, it can be as short as the one last code snippet I posted, as it will just replace the div of the button. The j is escaping "carriage returns and single and double quotes for JavaScript segments" https://api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html#method-i-escape_javascript But I found a better answer here: https://stackoverflow.com/a/12518779/9595653 – Clara May 30 '19 at 04:08
  • 1
    @Clara Thanks for this great post, got me closer to what I'm trying to accomplish. I have a unique issue in that what I am trying to update is the value of one within a large table. I haven't been able to figure out how to specifically target the unique element I need to update. If I append some kind of data-* attribute that I send to the controller, is there a way for me to access that in the JS response? At best the ID of the element is dynamic and I can't figure out how to target the right one. Any help greatly appreciated! – justinraczak Sep 25 '19 at 21:54
  • You're almost there. Assign a dynamic data attribute to the elememt like so: `` and then query it in the `js.erb` file like so `document.querySelector("[data-record-id='<%= @record.id %>']")` given that in the respective controller you have assigned this instance variable (because you know, you're js file get's rendered because you go through the controller). Hope it helps! – Clara Sep 26 '19 at 04:41
2

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

Nader-a
  • 113
  • 6
  • Yes basically I want to update the button after the ajax request goes through (to change color for example to denote that the user liked the post). So this can go right in the .erb file? – msmith1114 May 29 '19 at 07:26
  • 1
    Yes, it’s just a script, you can put it in the erb file(in a – Nader-a May 29 '19 at 17:06