rails: accessing a non-instance variable in js.erb

2019-09-08 06:31发布

I have a page that renders multiple forms. Currently, when the user submits any one of these forms, it updates (via ajax) a div on the same page with the content from the form that was just submitted.

I also want to remove() the form element that was just submitted after the ajax post request is completed. However, I need to be able to access that specific form ID within the js.erb file to do so.

Since my page has x number of forms rendered dynamically, I cannot simply access an instance variable in my js.erb.

Page:

<% for peer_review in @peer_reviews %>
    <%= render :partial => 'form', :locals => { :peer_review => peer_review } %>
<% end %>

<div id="completed_peer_reviews">
    <%= render 'completed_peer_reviews' %>
</div>

The @peer_reviews instance variable contains an array of new PeerReview objects already containing some data.

Form:

<div id="peer_review_form_<%= peer_review.reviewee_id %>">
<%= form_for peer_review, :html => { :method => "post" }, :remote => true do |f| %>
  <%= f.error_messages %>
  <p>
    Peer Review for: <%= User.find(peer_review.reviewee_id).name %><br />
  </p>
  <p>
    <%= f.label :rating %>: 
    <%= f.select :rating, [1, 2, 3, 4, 5], { :include_blank => 'None' } %>
  </p>
  <p>
    <%= f.label :review %><br />
    <%= f.text_area :review %>
  </p>

  <%= f.hidden_field :user_id, :value => peer_review.user_id %>
  <%= f.hidden_field :reviewee_id, :value => peer_review.reviewee_id %>
  <%= f.hidden_field :review_period_id, :value => peer_review.review_period_id %>

  <p><%= f.submit "Submit" %></p>
<% end %>
</div>

js.erb:

$("#completed_peer_reviews").html("<%= escape_javascript(render('completed_peer_reviews')) %>");

I was hoping to just add another line to the js.erb file that removes the form element that just triggered the execution of the js.erb file like so:

$("#peer_review_form_<%= peer_review.reviewee_id %>").remove();

How should I actually be referencing peer_review.reviewee_id here? Or should I be taking a completely different approach?

1条回答
你好瞎i
2楼-- · 2019-09-08 07:26

This is one of the classic issues of RJS templates.

Quick answer:

If you simply want to solve the problem, you could pass along some temporary id to identify the form. e.g:

# in the index
<% @peer_reviews.each.with_index do |peer_review, i| %>
  <%= render :partial => 'form', 
             :locals  => { :peer_review => peer_review, :i => i } %>
<% end %>

# then in the form (note, you don't need to specify POST in a form_for)
<div id="peer_review_form_<%= i %>">
  <%= form_for peer_review, :remote => true do |f| %>
    <%= hidden_field_tag 'temp_id', i %>

# finally in the create js.erb
$("#peer_review_form_<%= params[:temp_id] %>").remove();

Longer Answer:

That being said, while RJS templates were "the Rails way" for a long time, they've since fallen out of favor.

The more modern method is typically client side JS templates with a JSON API, rather than running server generated JS templates (RJS). This has a lot of advantages, one being that the DOM binding issue you're having right now no longer exists.

This is an example of how you might do this with pure jQuery, but there are many templating options out there.

<script id="peer_review_tmpl" type="text/x-jquery-tmpl">
  <div class="completed_peer_review">
    <p>${review}</p>
    ...
  </div>
</script>

Then you'd create a handler and bind it to a successful ajax response. This would require that your peer_reviews#create action responded to JSON:

$('form.new_peer_review').bind("ajax:success", function(data) {
  // remove the form which triggered the creation        
  $(this).remove();

  // then render the data into a template, and append it to the list
  $("#peer_review_tmpl").tmpl(data).appendTo("#completed_peer_reviews"); 
});
查看更多
登录 后发表回答