Render partial after ajax call without rails helpers (using webpack) - javascript

In the show.html.erb page I have a list of items:
<div id="shopOffersPartial">
<%= render "shops/shop_offers", offers: #offers %>
</div>
In the partial, there is simply a loop. #offers come from the backend
<% offers.each do |offer| %>
<%= render "shared/mini_offer_card/content", offer: offer, shop: #shop %>
<% end %>
I want to filter the element son every key up event. For that, I listen to an input. I have the JS logic in Webpack.
const shopFilterProductsInput = document.getElementById("shopFilterProducts");
const shopId = shopFilterProductsInput.dataset.shopid;
const shopOffersPartial = document.getElementById("shopOffersPartial");
const filterOfferes = (e) => {
let inputValue = shopFilterProductsInput.value;
const url = `/shops/${shopId}?query=${inputValue}`;
fetch(url)
.then(function() {
shopOffersPartial.innerHTML = "<%= render 'shops/shop_offers', offers: #offers %>";
})
.catch(function() {
// This is where you run code if the server returns any errors
});
}
if (shopFilterProductsInput) {
shopFilterProductsInput.addEventListener("keyup", filterOffers)
}
My question is in this part of the code:
fetch(url)
.then(function() {
shopOffersPartial.innerHTML = "<%= render 'shops/shop_offers', offers: #offers %>";
})
Once I get the response, I want to re-render the partial which has the list of items.
In rails, with .js.erb you can do things like that:
// app/views/reviews/create.js.erb
// Here you generate *JavaScript* that would be executed in the browser
function refreshForm(innerHTML) {
const newReviewForm = document.getElementById('new_review');
newReviewForm.innerHTML = innerHTML;
}
function addReview(reviewHTML) {
const reviews = document.getElementById('reviews');
reviews.insertAdjacentHTML('beforeend', reviewHTML);
}
<% if #review.errors.any? %>
refreshForm('<%= j render "reviews/form", restaurant: #restaurant, review: #review %>');
<% else %>
addReview('<%= j render "reviews/show", review: #review %>');
refreshForm('<%= j render "reviews/form", restaurant: #restaurant, review: Review.new %>');
<% end %>
But I am in a Webpack file. I can't use the Rails helpers.
How can I render then a Rails helper using Webpack?

This code
fetch(url)
.then(function() {
shopOffersPartial.innerHTML = "<%= render 'shops/shop_offers', offers: #offers %>";
})
should be replaced with this:
fetch(url)
.then(function(res) {
return res.text();
}).then(function(html) {
shopOffersPartial.innerHTML = html;
});
You don't have to use render in the JS file. The controller accessed by /shops/${shopId}?query=${inputValue} should return the needed html. Something like this:
def show
# offers = ???
# ...
respond_to do |format|
format.html { render 'shops/show' }
format.js { return plain: render_to_string("shops/shop_offers", offers: offers, layout: false) }
end
end

Related

RoR and json: No refresh on button click and updating count

I am trying to get follow buttons to change without refreshing the page on click. The following code works but only for the first post in the loop I am rendering in the view. The rest don't change/work.
In my view
<% #tweets.reverse.each do |tweet| %>
...
<% if current_user.id != tweet.user.id %>
<% if current_user.following?(tweet.user) %>
<%= link_to "Unfollow", relationships_path(user_id: tweet.user), data: { remote: true, type: :json, method: :delete }, :class => "follow-btn btn btn-primary" %>
<% else %>
<%= link_to "Follow", relationships_path(user_id: tweet.user), data: { remote: true, type: :json, method: :post }, :class => "follow-btn btn btn-primary" %>
<% end %>
<br>
<% end %>
<hr/>
<% end %>
<% end %>
application.js
$(document).on('ajax:success', '.follow-btn', function(event){
let $el = $(this);
let method = this.dataset.method;
if (method === 'post') {
$el.text('Unfollow');
this.dataset.method = 'delete';
} else if (method === 'delete') {
$el.text('Follow');
this.dataset.method = 'post';
}
});
How can I make it to update all the follow buttons that point to the same user in the loop?
Here is the newer code
application.js
$(document).on('ajax:success', '.follow-btn', function(event){
let $el = $(this);
let method = this.dataset.method;
if (method === 'post') {
$('.follow-btn[href="'+this.href+'"]').each(function(el){ $(el).text('Unfollow'); });
this.dataset.method = 'delete';
} else if (method === 'delete') {
$('.follow-btn[href="'+this.href+'"]').each(function(el){ $(el).text('Follow'); });
this.dataset.method = 'post';
}
});
Controller
def create
current_user.follow(#user)
respond_to do |format|
format.html
format.json { head :created }
end
end
def destroy
current_user.unfollow(#user)
respond_to do |format|
format.html
format.json { head :no_content }
end
end
Here the buttons still work but now don't change in the view. How can I get this to work?
Count
On the same view but rendered through a partial (users/_search_users.html.erb) I count like so. How can I make it so this count also updates without page refresh on button click?
<% #users.each do |user| %>
...
<td stlye="padding:0 60px 0 60px;" col width="130" align="right"><b><%= user.followers.count %> Followers</b></td>
<% end %>
I'd like to get both the button and count to update on click without refresh. I can add more code if needed.
ty
$(document).on('ajax:success', '.follow-btn', function(e) {
// the JSON fetched
let data = e.details[0];
// the method we are changing to
let method = this.dataset.method === 'post' ? 'delete' : 'post';
// lookup table for the text
let txt = {
post: 'Follow',
delete: 'Unfollow'
}[method];
// loop through elements with the same href
$(`.follow-btn[href="${this.getAttribute('href')}"]`).each(function() {
// change the attributes of the single node in the loop
this.dataset.method = method;
$(this).text(`${txt} (${data.count})`);
});
});
// This mocks the ajax call purely for the sake of this stacksnippets example.
// Do not include this in your actual implementation
$(document).on('click', 'a[data-remote]', function(e) {
window.setTimeout(function() {
let event = jQuery.Event('ajax:success');
event.details = [{ count: 5 }, 'No Content'];
$(e.target).trigger(event);
}, 25);
e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<p>User 1</p>
Follow
Follow
</div>
<div>
<p>User 2</p>
Follow
Follow
</div>
You can provide counts in the JSON responses by using render json::
def create
current_user.follow(#user)
respond_to do |format|
format.html
format.json do
render json: { count: #user.followers.count },
status: :created
end
end
end
def destroy
current_user.unfollow(#user)
respond_to do |format|
format.html
format.json do
render json: { count: #user.followers.count },
status: :ok
end
end
end
I'm just guessing the association so adjust according to your models.

Load form object on fetching from ajax

I have a form that renders partial 'form'.
= form_for(#booking, :as => :booking, :url => admin_bookings_path, :html => { :multipart => true }) do |f|
= render partial: "form", locals: { f: f }
Inside the partial form again another partial is rendered based on new_record?.
- if f.object.new_record?
#extra-products
= render partial: 'new_booking_extra_product', locals: { f: f }
- else
= render partial: 'extra_product', locals: { f: f }
In bookings#new, when the user selects particular car_id, I want to show associated products with it through ajax. For that I have used ajax to make a request to bookings#fetch_extra_product.
# Ajax request to fetch product
$('#booking_car_id').on('change', function(e){
var selectedCarId = $("#booking_car_id option:selected").val();
var url = "/admin/bookings/" + selectedCarId + "/fetch_extra_product";
$.ajax(url,{
type: 'GET',
success: function(msg){
},
error: function(msg) {
console.log("Error!!!");
}
});
});
# Bookings#fetch_extra_product
def fetch_extra_product
#car_id = params[:car_id] || Car.all.order('id desc').first.id
#extra_product = Car.find(#car_id).extra_products
respond_to do |format|
format.js
end
end
The fetch_extra_product.js.erb looks as follow:
$('#extra-products').html('$("<%= j render(:partial => 'new_booking_extra_product', :locals => {:f => f}) %>")');
But the form object (f) is undefined on this state. What's the best way to fetch the object or the best way to get through this problem?
You'll want to render a partial view with the associated products server-side when the Ajax request is received. Then you can send the HTML from that partial as part of your response and use the Ajax success callback to attach the partial view to the DOM however you want it. Then you can have (in your controller) code something like this:
def fetch_extra_product
# Unless you're using #car_id in a view or something, I'd just
# just use a local variable instead of an instance variable
#
car_id = params[:car_id] || Car.all.order('id desc').first.id
#extra_products = Car.find(car_id).extra_products
respond_to do |format|
format.js { render partial: 'extra_products_div', status: :ok }
end
Then your partial code is something like this:
<div class="extra-products">
<% #extra_products.each do |product| %>
<h2><%= product.name %></h2>
...
<% end %>
</div>
That way, your JavaScript can then go along the lines of:
$.ajax(url, {
type: 'GET',
success: function(msg) {
$('#foo').attach(msg);
},
error: function(msg) {
console.log('Error!!!');
}
});
One other comment: If your API is only going to get a request to this route via Ajax, you don't need the respond_to block. In that case you can just put the render partial: 'extra_products_div', status: :ok under the line where you
define #extra_products.

Empty body when respond_with used in Rails 5

I am trying to render a partial from a controller in rails 5 using ajax(on success) but the blank data is passed to ajax after the action renders a partial.
The Same code was working with rails4. I have also updated the responder gem too for rails5.
Controller:
respond_with do |format|
format.html do
if request.xhr?
#images = get_images
session[:prev_images] = submitted_ids
session[:prev_winner] = winner_id
#prev_images = Lentil::Image.find(submitted_ids).sort_by{ |i| i.id }
#prev_winner = session[:prev_winner]
render :partial => "/lentil/thisorthat/battle_form", :locals => { :images => #images, :prev_images => #prev_images, :prev_winner => #prev_winner }, :layout => false, :status => :created
end
end
Ajax:
$(document).on('ajax:success', function (evt, data, status, xhr) {
// Insert new images into hidden div
$('.battle-inner:eq(1)').replaceWith(data);
$('.battle-inner:eq(1)').imagesLoaded()
.done(function () {
// Remove old images
$('.battle-wrapper:eq(0)').remove();
// Div with new images moves up in DOM, display
$('.battle-wrapper:eq(0)').show();
// Hide Spinner
$('#spinner-overlay').hide();
});
_battle_form:
<div class="grid battle-inner">
<%= semantic_form_for :battle, :remote => true, :url => thisorthat_result_path do |form| %>
<% #images.each do |image| %>
<div class="battle-image-wrap grid__cell">
</div>
<% end %>
<% end %>
</div>
Is your request processing as JS?
Try replacing format.html to format.js
It was an issue with jquery-ujs which is no more a dependency for rails>5.1. rails-ujs dependency needs to be used instead.
https://blog.bigbinary.com/2017/06/20/rails-5-1-has-dropped-dependency-on-jquery-from-the-default-stack.html
//To read data from response
.on('ajax:error', function(event) {
var detail = event.detail;
var data = detail[0], status = detail[1], xhr = detail[2];
// ...
});

Ajax request in rails 4 to update value

I have a vote button, which simply displays the amount of votes and by clicking on it, a vote is automatically added. Now I would like to add an Ajax request, so that the page doesn't refresh. Unfortunately I have never used Ajax before and therefor have no idea how to use it with Rails. I tried going through a few tutorials, but nothing seems to help.
view:
<%= link_to vote_path(:the_id => Amplify.all.where(question_id: question.id)[0].id, :the_user_id => #current_user.id), :remote => true do %>
<button class="fn-col-2 fn-col-m-3 amplify">
<%= image_tag "logo-icon-white.png" %>
<p class="count"><%= Amplify.all.where(question_id: question.id)[0].users.count %></p>
</button>
<% end %>
controller:
def vote
#amplify = Amplify.find(params[:the_id])
#current_user = User.find(params[:the_user_id])
if #amplify.users.where(:id => #current_user.id).count != 0
#amplify.users.delete(#amplify.users.where(:id => #current_user.id).first)
else
#amplify.users << #current_user
end
#question = Question.where(id: #amplify.question_id)[0]
#amplify.update(:votes => #amplify.users.count)
#question.update_attributes(:votes => #amplify.votes)
redirect_to root_path
end
routes:
get '/vote' => "amplifies#vote", :as => "vote"
jQuery(document).ready(function($){
$('.amplify').on('click', function(){
var that = $(this);
$.ajax({
url: '/vote',
data: {id: 'your id'},
/**
* Response from your controller
*/
success: function(response) {
that.siblings('.count').first().text(response);
}
});
})
})
I like calling it like waldek does but with coffeescript.
However, in your example, you are using :remote => true which is the unobtrusive way
Basically you are then going into the controller where you will need a format.js
respond_to do |format|
format.html # if you are using html
format.js # this will call the .js.erb file
end
Then create a file vote.js.erb where you can then access your instance variables and write js
console.log('you are here');

dynamically displaying data in text field ruby on rails

Hello guys i am trying attempt a dynamic select here. as soon as i select the customer his total value in the bill should come and get displayed in the text field tag.
the view
jQuery(document).ready(function(){
jQuery(".customerid").bind("change", function() {
var data = {
customer_id: jQuery(".customerid :selected").val()
}
jQuery.ajax({
url: "get_cust_bill",
type: 'GET',
dataType: 'script',
data: data
});
});
});
</script>
<div class ="customerid"><%= f.label :customer_id %>
<%= f.collection_select :customer_id, Customer.all, :id, :name, options ={:prompt => "-Select a Customer"}, :class => "state", :style=>'width:210px;'%></div><br />
<div class ="customerbill">
<%= f.label :total_value, "Total Value" %>
<%= render :partial => "customerbill" %>
js.erb file
jQuery('.customerbill').html("<%= escape_javascript(render :partial => 'customerbill') %>");
the customerbill partial
<% options = []
options = #cust_bill.total_value if #cust_bill.present? %>
<%= text_field_tag "total_value", options %>
in contoller
def get_cust_bill
#cust_bill = CustomerBill.find_all_by_customer_id(params[:customer_id]) if params[:customer_id]
end
I feel the problem lies in the partial, the way i am calling the options so can anyone guide me how to get the value in text field??? thank in advance.
From what I understand, total_value text field does not show anything. Could you try to output the value of options and check if it always has a value? I suggest you check out the documentation for the text_field_tag. Basically, it accepts three variables like this:
text_field_tag(name, value = nil, options = {})
i was using getJSON method....and i feel that can be used here. hope the followng works.
jQuery(document).ready(function()
{
jQuery(".customerid select").bind("change", function() {
var data = {
product_id: jQuery(this).val()
}
jQuery.getJSON(
"/controller_name/get_cust_bill",
data,
function(data){
var result = "";
res = parseFloat(a[1]);
jQuery('.price input').val(res);
});
});
});
controller
def get_cust_bill
#cust_bill = CustomerBill.find_all_by_customer_id(params[:customer_id]).map{|p| [p.price]} if params[:customer_id]
respond_to do |format|
format.json { render json: #cust_bill }
end
end
so no need of calling js. erb partial you can simply have
<div class = "price"><%= f.input :price, :label => 'Price', :input_html => { :size => 20} %></div><br/>
all the best :)

Categories

Resources