AJAX call being handled but not rendering in rails view - javascript

In a rails 4.2 application, a div lists and renders existing cartitems
<div id='yield_cartitem'>
<%= render 'cartitems' %>
</div>
and the same page has forms (one per listed products).
<%= form_for(#cartitem, remote: true, id: 'data-js-cartitem-form', data: {'js-cartitem-form' => true}) do |f| %>
<%= f.hidden_field :product_id, value: product.id %>
<%= f.hidden_field :price, value: product.price %>
<%= f.submit 'Add to cart' %>
<% end %>
The javascript for the class defines via jQuery
$(document).on('turbolinks:load', function() {
$('[data-js-cartitem-form]').on("ajax:success", function(event, data, status, xhr){
var cartitem = $(xhr.responseText).hide();
$('#cartitems').append(cartitem);
cartitem.fadeIn(1000);
document.getElementById('data-js-cartitem-form').reset();
});
$('#cartitems').on("ajax:success", function(event, data, status, xhr){
var cartitem_id = xhr.responseJSON.id;
$('[data-js-cartitem-id=' + cartitem + ']').hide();
});
});
The controller create action runs as by design with the following rendering instruction.
if #cartitem.save
render partial: 'cartitem', locals: {cartitem: #cartitem}
end
_cartitems.html.erb has
<div id=cartitems class='tableize'>
<%= render partial: 'cartitem', collection: #cartitems %>
</div>
while _cartitem.html.erb defines
<div class='row' data-js-cartitem-id=<%= cartitem.id %>>
<%= cartitem.quantity %>
<%= cartitem.um %>
<%= cartitem.product.try(:name) %>
<%= cartitem.price %>
</div>
_cartitem.js.erb calls $("div#yield_cartitem").html('< %=j (render 'cartitem') %>');
The XHR response payload is returning $("div#yield_cartitem").html('< %=j (render 'cartitem') %>'); but the div is not refreshing with the newly created cartitem.
Where has this gone wrong?
update
Trying to simplify matters by changing the _cartitems.js.erb to an alert:
alert("<%= cartitem.quantity %> <%=j cartitem.um %> <%=j cartitem.product.try(:name) %> <%= number_to_currency(cartitem.price) %> Added")
The alert does effectively render.
alert("1.0 kg Prod 1,89 € Added")

I may be wrong but I noticed that in controller you are passing locals to cartitem html partial but not in js.erb file. So do that in cartitem.js.erb file:
$("div#yield_cartitem").html('< %=j (render 'cartitem', cartitem: cartitem) %>');
it's really hard to debug js.erb files but it's possible. Try to use gem pry to see context and variables inside the partial.
$("div#yield_cartitem").html('<%= binding.pry; j(render 'cartitem', cartitem: cartitem) %>');

$("div#yield_cartitem").html('<%=j (render 'cartitem') %>') this will replace the contents of #yield_cartitem
instead use $('#cartitems').append('<%= j render('cartitem', cartitem: #cartitem) %>')
and update this function
$('[data-js-cartitem-form]').on("ajax:success", function(event, data, status, xhr){
// var cartitem = $(xhr.responseText).hide();
// $('#cartitems').append(cartitem);
// cartitem.fadeIn(1000);
document.getElementById('data-js-cartitem-form').reset();
});
Additionally rename _cartitem.js.erb to create.js.erb then create will render it automatically

Related

'undefined local variable or method' with render partial rails 5

I want to render a modal form and be able to render it on differents places; hence I want to be able to change the order_id that is required to create a new feedback on server side (I use AJAX).
However, my render partial does not work. I get the following error:
undefined local variable or method `user_order_id' for #<#<Class:0x007fec6be26af8>:0x007fec6bf1b2d8>
any Idea ?
index.html.erb
[...]
<%= render partial: 'feedback_modal', locals: {user_order_id: #user_last_order} %>
[...]
_feedback_modal.html.erb
<%= simple_form_for(#review, method: :post, url: review_path(current_user.id, user_order_id), remote: true) do |f| %>
<%= f.input :rating, collection: [1,2,3,4,5], prompt: "Rate this meal", class: "col-sm-3" %>
<%= f.label :comment, "Your comments" %>
<%= f.text_area :comment, class: "commentaire" %>
<%= f.button :submit ,"Send Feedback", class: "btn" %>
<% end %>
controller:
[...]
#user_last_oder = Order.where(user_id: current_user.id, status: nil).last.id
[...]

Complex Cascading Dropdown in Rails won't work in Edit Form

I recently tried to implement a cascading dropdown into my application with this tutorial on petermac.com: http://www.petermac.com/rails-3-jquery-and-multi-select-dependencies/
The tutorial basically talks about doing a cascading dropdown, where every dropdown box is an a separate partial and gets loaded with an jQuery onChange event when the parent select is changed.
Now I got this to work without a problem. But actually my select boxes have quite complicate relationships between them.
So, the form I belongs to a Model called AuditFunction and as the name says is for auditing. Every audit has a source and a target, which can be compared via several commands. The source as well as the target are selected via 3 select boxes. The first box selects the type of database the field is in. The second box selects the table and then the third box selects the field. As the field box can contain thousands of options I tried to implement the cascading dropdown to make it easier for the user to select the field.
To give you an overview, this is what my actions look like:
# new.html.erb
<%= simple_form_for #audit_function do |f| %>
<%= f.input :database_1, :as => :select, :collection => #databases, :include_blank => true %>
<%= render :partial => 'databases_1' %>
<%= render :partial => 'fields_1' %>
<%= f.input :database_2, :as => :select, :collection => #databases, :include_blank => true %>
<%= render :partial => 'databases_2' %>
<%= render :partial => 'fields_2' %>
<% end %>
The javascript for this looks like this:
# jQuery
<script type='text/javascript' charset='utf-8'>
jQuery(function($) {
// when the #country field changes
$("#audit_function_database_1").change(function() {
var database_1 = $('select#audit_function_database_1 :selected').val();
if(database_1 == "") database_1="0";
jQuery.get('/audit_functions/update_database_1_id_select/' + database_1, function(data){
$("#database_1_id").html(data);
})
return false;
});
})
</script>
<script type='text/javascript' charset='utf-8'>
jQuery(function($) {
// when the #country field changes
$("#audit_function_database_2").change(function() {
var database_2 = $('select#audit_function_database_2 :selected').val();
if(database_2 == "") database_2="0";
jQuery.get('/audit_functions/update_database_2_id_select/' + database_2, function(data){
$("#database_2_id").html(data);
})
return false;
});
})
Now I'm only going to show you the partials for database_1_id and field_1_id, but they look the same as database and field 2.
# _databases_1.html.erb
<script type="text/javascript">
jQuery(function($) {
$("#audit_function_database_1_id").change(function() {
var database_1_id = $('select#audit_function_database_1_id :selected').val();
if(database_1_id == "") database_1_id="0";
jQuery.get("/audit_functions/update_field_1_id_select/" + ("<%= params[:id] %>_" + database_1_id), function(data){
$("#field_1_id").html(data);
})
return false;
});
})
</script>
<%= simple_form_for "audit_function" do |f| %>
<% if params[:id] %>
<% if params[:id] == "imp" %>
<%= f.input :database_1_id, collection: AdOriTbl.all.order(ori_filename: :asc).collect{ |a| [a.ori_filename,a.id]} %>
<% elsif params[:id] == "ori" %>
<%= f.input :database_1_id, collection: AdOriTbl.all.order(otb_filename: :asc).collect{ |a| [a.otb_filename,a.id]} %>
<% elsif params[:id] == "mod" %>
<%= f.input :database_1_id, collection: AdQryMod.all.order(qry_mod_text: :asc).collect{ |a| [a.qry_mod_text,a.id]} %>
<% end %>
<% end %>
<% end %>
And now the file containing the target field.
# _fields_1.html.erb
<%= simple_form_for "audit_function" do |f| %>
<% if params[:id] %>
<% if params[:id].gsub(/_{1}\d{1,}\z/, "") == " mod " %>
<%= f.input :field_1_id, collection: AdQryFld.where(ad_qry_mod_id: params[:id].gsub(/\A\w{1,}_{1}/, "").to_i).order(order_id: :asc).collect{ |f| [f.qry_field_text,f.id]} %>
<% else %>
<%= f.input :field_1_id, collection: AdOriFld.where(ad_ori_tbl_id: params[:id].gsub(/\A\w{1,}_{1}/, "").to_i).order(id: :asc).collect{ |f| [f.otb_colhdg,f.id]} %>
<% end %>
<% end %>
<% end %>
The controller then contains all the actions triggered in the javascripts:
# audit_function_conroller.rb
def new
authorize! :new, :audit_functions
#audit_function = AuditFunction.new
#functions = [[I18n.t("text sum"),"sum"],[I18n.t("text quantity"),"quantity"],[I18n.t("text largest_value"),"largest_value"],[I18n.t("text smallest_value"),"smallest_value"]]
#databases = [[I18n.t("text original_database"),"imp"],[I18n.t("text archive_database"),"ori"],[I18n.t("text query_database"),"mod"]]
end
def update_database_1_id_select
if params[:id] == "mod"
type = "mod"
elsif params[:id] == "ori"
type = "ori"
elsif params[:id] == "imp"
type = "imp"
end
render partial: "databases_1", id: type
end
def update_field_1_id_select
type = params[:id]
render partial: "fields_1", id: type
end
Now, as messy as all of this looks, the good thing is that it gets the job done. And to clarify my MVC, these are the relations:
AdOriTbl has_many AdOriFlds
AdOriFld belongs_to AdOriTbl
AdQryMod has_many AdQryFlds
AdQryFld belongs_to AdQryMod
I hope the names don't bother you too much when reading this.
Now lets get back to the problem:
As I said this code works for creating a new object and everything is selected fine. But when I try to edit an object only the field with the database type (database_1 and database_2) are filled. The select boxes for the ID's of the databases are not rendered, while the boxes for the fields are. But all four ID fields are empty.
Now I already tried to fill the boxes by hand with a jQuery that basically looks similar to the ones I already have, but instead of getting triggered onChange, I trigger it when my audit_function has a database_id and render the select box and fill it with the value according value of database_id. This works as well.
The problem is that I can't do this with the field_id, because in the partial of database_1_id where the jQuery for the fields get triggered, I don't have the #audit_function object at hand and also it seems to interfere with the other javascripts.
Besides that I'd also like to think that there is a better way to do this, then my way. But I already tried other tutorials and ways and they either don't work when you don't have your straight-forward Country-State-City relationships or they don't work when editing.
So, any help would be really appreciated. Thanks!
I took the following tutorial as template to rewrite my cascading dropdown:
http://homeonrails.blogspot.de/2012/01/rails-31-linked-dropdown-cascading.html
So, now I throw all the different models into one array and filter it by appending names to the class, to differentiate not only by ID, but also by name. Also the jQuery Plugin chainedTo makes the code much more readable.
So, the controller looks now like this:
#types_for_dropdown = [[I18n.t("text archive_database"),"ori"],[I18n.t("text query_database"),"mod"]]
#tables_for_dropdown = []
#ad_qry_mods = AdQryMod.all
#ad_qry_mods.each do |i|
#tables_for_dropdown = #tables_for_dropdown << [i.qry_mod_text,"mod#{i.id}",{:class => "mod"}]
end
#ad_ori_tbls = AdOriTbl.all
#ad_ori_tbls.each do |i|
#tables_for_dropdown = #tables_for_dropdown << [i.otb_filename,"ori#{i.id}",{:class => "ori"}]
end
#fields_for_dropdown = []
#ad_qry_flds = AdQryFld.all
#ad_qry_flds.each do |i|
#fields_for_dropdown = #fields_for_dropdown << [i.qry_fieldname,i.id,{:class => "mod#{i.ad_qry_mod_id}"}]
end
#ad_ori_flds = AdOriFld.all
#ad_ori_flds.each do |i|
#fields_for_dropdown = #fields_for_dropdown << [i.otb_fieldname,i.id,{:class => "ori#{i.ad_ori_tbl_id}"}]
end
And the form looks like this:
<%= content_for :head do %>
<script>
$(document).ready(function(){
$('select#audit_function_database_1_id').chainedTo('select#audit_function_database_1');
$('select#audit_function_field_1_id').chainedTo('select#audit_function_database_1_id');
$('select#audit_function_database_2_id').chainedTo('select#audit_function_database_2');
$('select#audit_function_field_2_id').chainedTo('select#audit_function_database_2_id');
});
</script>
<% end %>
<div class="grid-6-12">
<%= f.input :database_1, label: I18n.t("field_label audit_function database_1"), hint: I18n.t("field_hint audit_function database_1"), as: :select, collection: #types_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_2, label: I18n.t("field_label audit_function database_2"), hint: I18n.t("field_hint audit_function database_2"), as: :select, collection: #types_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_1_id, label: I18n.t("field_label audit_function database_1_id"), hint: I18n.t("field_hint audit_function database_1_id"), as: :select, collection: #tables_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_2_id, label: I18n.t("field_label audit_function database_2_id"), hint: I18n.t("field_hint audit_function database_2_id"), as: :select, collection: #tables_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :field_1_id, label: I18n.t("field_label audit_function field_1_id"), hint: I18n.t("field_hint audit_function field_1_id"), as: :select, collection: #fields_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :field_2_id, label: I18n.t("field_label audit_function field_2_id"), hint: I18n.t("field_hint audit_function field_2_id"), as: :select, collection: #fields_for_dropdown, include_blank: true %>
</div>
This is really a nice solution and I can recommend it to everyone!

Dynamically add fields in rails with out nested attributes

I am in the early stages of creating an app, and am just putting some basic code in place. Here is the current code...
app/views/cards/front.html.erb
<%= form_for(front_of_card_path) do |f| %>
<%= f.fields_for :competency_templates do |builder| %>
<%= render 'add_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add New Tag", f, :skill %>
<% end %>
routes
controller :cards do
get '/front', action: 'front', as: 'front_of_card'
post '/save', action: 'create', as: 'save_card'
get '/my_contact_info', action: 'back', as: 'back_of_card'
put '/save', action: 'update', as: 'save_card'
get '/my_card', action: 'show', as: 'card'
end
controller
def create
#skill= Skill.new(params[:skill])
#tag = Tag.new(params[:tag])
#tag.save
#skill.tag_id = #tag.id
#skill.save
redirect_to front_of_card_path, notice: 'Skill was successfully created.'
#get user/session
#save skills & tags
end
cards.js.coffee
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
app_helper
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
So right now this code gives me two text fields. One for the a tag name and another for a tag weight, and the controller inserts everything in the DB. I would like use some javascript to dynamically add as many of these tag/weight fields as I like. Everything I've found seems to focus on nested attributes. Any ideas appreciated.
Update
Added more code to flesh this out. The issue I am having is the 3rd variable I am passing in on this line...
<%= link_to_add_fields "Add New Tag", f, :skill %>
It does not like ':skill', but I am not sure what I should be passing here.
So here is what I came up with...here are my two models...
class Skill < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :weight
end
class Tag < ActiveRecord::Base
has_many :skills
attr_accessible :name
end
I'm calling a partial from app/views/skills/_form.html.erb and using a js tag to add new fields. Also note that I am re-rendering the partial, then hiding it in the last div tag.
<div id="skillSet">
<%= render partial: "skills_form" %>
</div>
Add New Tag
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<div class="hide" id="new_skills_form">
<%= render partial: "skills_form", locals: {skill: false} %>
</div>
The partial is pretty simple. All I am doing here is storing the values in an array...
<div class="skillsForm">
<%= label_tag 'tag' %>
<%= text_field_tag 'tags[]' %>
<%= label_tag 'weight' %>
<%= text_field_tag 'weights[]' %>
</div>
...here is the javascript...real straight forward, just say when #addNewTag is clicked, appeand #new_skills_form to #skillSet
$(document).ready(function(){
$("#addNewTag").click(function(){
$("#skillSet").append($("#new_skills_form").html());
});
});
...and finally the controller action decontructs the arrays, and saves them...
def create
#skill = Skill.new(params[:skill])
tags = params[:tags]
weights = params[:weights]
tags.each_with_index do |tag, index|
tag = Tag.create :name => tag
Skill.create :tag_id => tag.id, :weight => weights[index]
end
end

AJAX Fave Button Not Working Rails

I'm trying to allow users to favorite posts and then it show them sort of of interaction through AJAX, but it's not working.
The error I'm getting in the console is:
ActionView::Template::Error (undefined local variable or method `post_item' for #<#<Class:0x007fecb2a3d5f8>:0x007fecb2a357e0>):
The button is being rendered through a partial:
<%= render "shared/fave_form", post_item: post_item %>
Here's the code for the button (shared/_fave_form.html.erb):
<% if current_user.voted_on?(Post.find(post_item)) %>
<%= link_to "unlike", vote_against_post_path(post_item.id), :remote => true, :method => :post, :class => "btn") %>
<% else %>
<%= link_to "like", vote_up_post_path(post_item.id), :remote => true, :method => :post, :class => "btn") %>
<% end %>
Here's the toggle.js.erb file:
$("#fave").html("<%= escape_javascript render('fave_form') %>");
When you render the partial using toggle.js.erb it is not getting locals value post_item, you have to provide it in also.So, your js code should be something like following
$("#fave").html("<%= escape_javascript(render :partial=>"fave_form", locals: {post_item: post_item}).html_safe %>);
I guess you are using some ajax call and then your toggle.js.erb so in your toggle action you must specify value to post_item, lets make it instance variable #post_item so that we can use it in toggle.js.erb.
$("#fave").html("<%= escape_javascript(render :partial=>"fave_form", locals: {post_item: #post_item}).html_safe %>);
The partial is using a local variable, so pass post_item as a local:
<%= render :partial => "shared/fave_form", :locals => {post_item: post_item} %>

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

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?
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");
});

Categories