So I wanted to play with a star ratings page, where I have 5 rating questions and you can rate once (create) and update your rating at any given time. The rating form is ajaxified but the problem is the form element stays as a Create method after submission instead of an update method.
I followed a pretty cool tutorial by eighty-b but there is one part that is missing about the form being updated to the proper method.
Here is the code
I have a form in a partial rating_questions/_quick_form.html.erb
with a nested form for each rating form. Note that the form route is using a helper to define if it should be a create or and update action
The rating_form.html.erb
partial
# EDIT: I added the temporary random id in an instance variable to make sure the form id and hidden_field have the same reference
<% @rand_id = SecureRandom.hex %>
<%= form_for(rating_ballot(rating_question), :remote => true, :html => { :class => 'rating_ballot', :id => @rand_id}) do |f| %>
<%= rating_question.title %>
<%= hidden_field_tag :temp_id, @rand_id %>
<%= f.label("value_#{rating_question.id}_1", content_tag(:span, '1'), {:class => "rating", :id => "1"}) %>
<%= radio_button_tag("rating[value]", 1, current_user_rating(rating_question) == 1, :class => 'radio_button', :id => "rating_value_#{rating_question.id}_1") %>
... (the other rating radio buttons) ...
<%= f.hidden_field("rating_question_id", :value => rating_question.id) %>
<%= f.submit :Submit, :id => "rating_submit" %>
<% end %>
Then in my create.js.erb
I added the line to replace the form with the partial
$('#<%= params[:temp_id] %>').replaceWith("<%= j render(partial: 'rating_questions/rating_form', locals: {:rating_question => @rating_question}) %>");
The helper methods for defining if the form should be a create or an update if there is an existing record
def rating_ballot(rating_question)
if @rating = current_user.ratings.find_by_rating_question_id(rating_question.id)
[rating_question, @rating]
else
[rating_question, current_user.ratings.new]
end
end
def current_user_rating(rating_question)
if @rating = current_user.ratings.find_by_rating_question_id(rating_question.id)
@rating.value
else
"N/A"
end
end
and my ratings_controller.rb
that calls for create.js.erb
and update.js.erb
def create
@rating_question = RatingQuestion.find_by_id(params[:rating_question_id])
@rating = Rating.new(params[:rating])
@rating.user_id = current_user.id
if @rating.save
respond_to do |format|
format.html { redirect_to rating_questions_path, :notice => "Your rating has been saved"}
format.js
end
end
end
def update
@rating_question = RatingQuestion.find_by_id(params[:rating_question_id])
@rating = current_user.ratings.find_by_rating_question_id(@rating_question.id)
if @rating.update_attributes(params[:rating])
respond_to do |format|
format.html { redirect_to rating_questions_path, :notice => "Your rating has been updated"}
format.js
end
end
end
Obviously I only want to update the form that was just submitted and not the other ones.
Any idea on how to reload the form with the proper method using Javascript in the create.js.erb and the update.js.erb?
Thank you very much!
EDIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(See the edits above)
I went ahead with your suggestions and I added the random id to the form and changed the create.js.erb, but I encountered problems:
1) After the ratings forms are replaced, the Javascript no longer works on that partial.
Here is also the coffee script for dynamic interactions of the stars (actually the form radio labels)
$ ->
# Add the "Bright class" to the stars for each checked radio button before the document is ready.
$("form.rating_ballot").each ->
checkedId = undefined
checkedId = $(this).find("input:checked").attr("id")
$(this).find("label[for=" + checkedId + "]").prevAll().andSelf().addClass "bright"
$(document).ready ->
# makes stars glow on hover.
$("form.rating_ballot > label").hover (-> # mouseover
$(this).prevAll().andSelf().addClass "glow"
), -> # mouseout
$(this).siblings().andSelf().removeClass "glow"
# makes stars stay glowing after click.
$("form.rating_ballot > label").click ->
$(this).siblings().removeClass "bright"
$(this).prevAll().andSelf().addClass "bright"
# Submits the form (saves data) after user makes a change.
$(".rating_ballot").change ->
$(this).submit()
EDIT 2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The fact that the update action was submitting all the form was corrected.
Answer
If I got you right...
1. Add a generated ID as a hidden field to your form
This ID does not really have to be a virtual attribute for your model but can be. Another option is to put it outside of your mass-assignment array, like e.g params[:controller]
also is.
Example: <%= hidden_field_tag :temporary_id, SecureRandom.hex %>
. For other possibilities on creating a random string, have a look at how-best-to-generate-a-random-string-in-ruby .
Also make sure that your form (or the first element which gets rendered in your form partial) has that generated random string as id
or data-
attribute, so you can access it without using a complicated selector in the next step.
2. Update your create.js.erb
I assume your format.js
in your create action passes over to create.js.erb
. So in there, you replace the whole form with a new one, by searching for that temporary id which got passed on submit.
Example: $('#
Make sure to not omit the j
helper as it encodes your rendered form for save use in javascript. As you are rendering your usual form, the create action does its usual business, and now also replaces the form with a new rendered one, where automatically, the "update" action is called (the usual rails "magic" stuff in routing, that is. like you'd render another usual edit form).
Javascript
After the ratings forms are replaced, the Javascript no longer works on that partial.
Of course it isn't. You're binding events to specific elements on document load. The elements you insert afterwards (via create/update) are newly added, and thus do not have that event binding because they were not available at the time of the event-binding taking place.
Since events are "bubbling", you need to bind your code to a container which is persistent across your js-modifications. Lets say your create.js only updates/replaces elements in #rating_questions_container
, you'd need to rewrite the js to e.g.
$('#rating_questions_container').on('click', 'form.rating_ballot > label', function(e) {
# code
});
$('#rating_questions_container')
is the "persistent" container on your page, that can capture the events.on('click', 'form.rating_ballot > label'
watches for click events on elements matching the selector in the second argument, hereform.rating_ballot > label
.
This allows to dynamically append/remove DOM nodes inside of #rating_questions_container
without losing event listeners.
No comments:
Post a Comment