Acts As Votable Ajax Vote Size Not Updating Rails - javascript

I am using the acts as votable gem on my rails 4 app.
I have finally gotten it to work using ajax, so the page does not refresh when a user votes, however, the vote.size doesn't update until the page is refreshed.
Here is my controller action:
def upvote
#article = Article.find(params[:article_id])
#subarticle = #article.subarticles.find(params[:id])
session[:voting_id] = request.remote_ip
voter = Session.find_or_create_by(ip: session[:voting_id])
voter.likes #subarticle
respond_to do |format|
format.html {redirect_to :back }
format.json { render json: { count: #subarticle.get_upvotes.size } }
end
end
and view:
<%= link_to like_article_subarticle_path(#article, subarticle), class: "vote", method: :put, remote: true, data: { type: :json } do %>
<button type="button" class="btn btn-success btn-lg" aria-label="Left Align" style="margin-right:5px">
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> Helpful
</button><span class="badge" style="margin-right:10px"><%= subarticle.get_upvotes.size %></span>
<% end %>
<script>
$('.vote')
.on('ajax:send', function () { $(this).addClass('loading'); })
.on('ajax:complete', function () { $(this).removeClass('loading'); })
.on('ajax:error', function () { $(this).after('<div class="error">There was an issue.</div>'); })
.on('ajax:success', function(e, data, status, xhr) { $(this).html("Thank you for voting!"); });
</script>
As of now, when a user votes, it completely get's rid of the "helpful" button and upvote size, and displays the html"Thank you for voting".
I am having trouble figuring out how to keep the button and simply update the upvote.size to the correct number. I have tried assigning a class to the upvote size and then using something like this: $('.item-class').html(data.count) but no luck.
Any recommendations? Thank you!

$('.item-class').html(data.count)
item-class doesn't seem to be declared in your template anywhere... but even if it did, if you call it like this, it will likely match more than one thing on the page, so this replace won't work.
The reason why the button is being replaced is you are replacing the html of "this" (which is defined as the whole section marked with the class .vote)
If you want to replace just the item-class within the .vote section (ie leave the button intact) then you need to a) add something with the class of 'item-class' and b) reduce what you are replacing to that. eg:
$(this).first('.item-class').html("Thank you for voting!");
(note I've not bug-tested this - you may need to google javascript first to make sure this is the correct usage).

Related

After javascript submits form in rails 6 the update action in controller not launching update.js.erb

Not sure if I am asking this question wrong, but I can't seem to find exactly the issue I am faced with.
I have a very basic rails 6 app for a task list. Tasks are either complete or incomplete and the change/update of this is to be done via javascript (the html side works just fine).
Here is my form partial _task.html.erb:
<%= form_with model: task, html: { class: "edit_task", id: "edit_task_#{task.id}" } do |f| %>
<%= f.check_box :complete %>
<%= f.submit "Update" %>
<%= f.label :complete, task.name %>
<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
<% end %>
Here is the javascript to submit the form, tasks.js
function submit_update(){
const checkboxes = document.querySelectorAll('.edit_task input[type=checkbox]');
const submitbutton = document.querySelectorAll('.edit_task input[type=submit]');
submitbutton.forEach(button => button.remove());
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => checkbox.parentElement.submit());
});
}
window.addEventListener('turbolinks:load', event => {
submit_update();
document.addEventListener('task_added', event => submit_update());
});
This part works just fine, but once submitted and based on this section of the controller
def update
#task.update!(task_params)
respond_to do |format|
format.html { redirect_to root_url, notice: 'Task successfully updated' }
format.js
end
end
My understanding is together this should launch update.js.erb, which currently looks like
unction update_task() {
const current_task = document.querySelector('#edit_task_<%= #task.id %>');
const complete_tasks = document.querySelector('#complete_tasks');
const incomplete_tasks = document.querySelector('#incomplete_tasks');
<% if #task.complete? %>
complete_tasks.insertAdjacentHTML('beforeend',current_task.innerHTML);
<% else %>
incomplete_tasks.insertAdjacentHTML('beforeend',current_task.innerHTML);
<% end %>
}
update_task();
I have tried changing the above to a single line using an alert call and it still never gets called.
If someone could let me know why update.js.erb is not being called, it would be much appreciated :)
If any additional information is required, please let me know?
EDIT:
On further testing, I have found that if I submit the update via the click of the button, ie remove the submission via javascript, that the update.js.erb is actioned correctly.
So it would seem the focus needs to be on the tasks.js file and how that submits?
What is weird is that when that is included, after the submit the HTML format runs just fine, just not the js format??
After a number of days of trying to get the correct search words I have found the solution and happy to submit for others to find :)
The magic can be found here
The change for my example is as follows --
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => checkbox.parentElement.submit());
});
// becomes
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', event => {
checkbox.parentElement.dispatchEvent(new Event('submit', {bubbles: true}));
});
});
I have since edited the update.js.erb (does the same thing just a bit cleaner), but it also
needed an additional entry for newly added items to trigger having the entry change positions:
$ cat app/views/tasks/update.js.erb
function update_task() {
const current_task = document.querySelector('#edit_task_<%= #task.id %>');
const to_task = document.querySelector('#<%= #task.complete? ? "complete_tasks" : "incomplete_tasks" %>');
// Remove from previous list
current_task.remove();
// Append to the end of new list
to_task.insertAdjacentHTML('beforeend', '<%= j render(#task) %>');
// Advise tasks.js that new entry needs listener added
to_task.dispatchEvent(new Event('task_added', { bubbles: true }));
}
update_task();
Hope others find this useful :)

Get different html with different buttons with AJAX

I got this menu on my website
%button.dropdown-button
.current-user{onclick:'showMenu()'}
%img.current-user-image{src:current_user.picture_url}
= current_user
%i.fa.fa-bars{'aria-hidden':true}
.dropdown-content
.menu-option{onclick:'showFilters()'}
Filter Transactions
%i.fa.fa-paper-plane{'aria-hidden':true}
.transaction-filters
.filter-option
My Transactions
%i.fa.fa-square-o
%a{href:'#'}
.menu-option
Balance History
%i.fa.fa-history{'aria-hidden':true}
%a{href:destroy_user_session_path}
.menu-option
Sign Out
%i.fa.fa-sign-out{'aria-hidden':true}
And I got this timeline with transactions
.timeline-container
- #transactions.each do |transaction|
.transaction-container
.transaction-header-container
.transaction-kudos-container
= "+#{transaction.amount}"
%span.currency
₭
.transaction-avatar-container
%div
= image_tag transaction.receiver_image, class:'avatar'
.transaction-body-container
.transaction-content
%span
= "#{transaction.sender.name}:"
%span.highlighted
= "+#{transaction.amount} ₭"
%span
to
%span.highlighted
= transaction.receiver_name_feed
%span
for
%span.highlighted
= transaction.activity_name_feed
%div
-#%button#like-button
-# Like
%span.post-time-stamp
= "#{distance_of_time_in_words(DateTime.now, transaction.created_at)} ago"
= paginate #transactions
They are both rendered on my index.html.haml
So when I click the div .filter-option.sent I want to change the code change from
- #transactions.each do |transaction|
to
- #all_transactions.each do |transaction|
to filter out the transactions of the current user without reloading the page.
These variables are defined in my controller
#transactions = Transaction.order('created_at desc').page(params[:page]).per(20)
#all_transactions = Transaction.all_for_user(current_user)
With in my model the method all_for_user
def self.all_for_user(user)
Transaction.where(sender: user).or(Transaction.where(receiver: user)).order('created_at desc').page.per(20)
end
I tried a lot of things with AJAX but nothing seems to please me. Somebody can help me?
So if you'd like to replace that #transactions list with AJAX you will need to do a few things..
1) Move the #transactions block into a partial that takes a local variable.
#transactions-wrapper
= render partial: 'transactions/list', locals: {transactions: #transactions}
2) Create a link that submits to a route, that hits a controller action as #js ('data-remote': true ) ( or write a javascript function that triggers $.ajax request: https://www.w3schools.com/jquery/ajax_ajax.asp
%a.transaction-filter{href: "/transactions/#{transaction_type}", 'data-remote': true}
or ex..
%a.study{href: "/transactions/recent", 'data-remote': true}
%a.study{href: "/transactions/past", 'data-remote': true}
3) define the route
get '/transactions/:type' => 'transactions#filter'
4) re-assign the variable based on the filter, and re-render that partial with the new data in a filter.js.erb file thats in the view directory -> app/views/transactions
def filter
#filtered_transactions = Transactions.where(type: params[:type] ).order('created_at DESC')
respond_to do |format|
format.html
format.js { render :action => 'filter.js.erb', :layout => false}
end
end
$("#transactions-wrapper").html("<%= j render(:partial => 'transactions/list'', :locals => { transactions: #filtered_transactions } ) %>");
alert('Transactions have been filtered, yo')
Let me know if that makes sense! Except please don't actually javascript alert at the end ;)

Ajax call and rails

I have to make an ajax call in my application. There is 3 different tabs (I use bootstrap tabs), and each tab call a request, and the different request are displaying in different tab-content. I create a loader which is display when I make the ajax call, for UI. But I have a problem, when I have clicked on a tab, the Ajax call is processing well, but when I click an other time on the same tab, the ajax call restart.
I juste want that data be loaded only one time, at the first request.
Here is the code :
show.html.erb (link for the call)
<li role="presentation" class="<%= 'active' if #school.is_subscribed? %>">
<%= link_to "Vérifiés (#{#count_verified})", verified_rating_path(school_id: #school.id), id: 'verified_link', 'data-toggle' => 'tab' , 'data-target' => '#verifie', remote: true %>
</li>
**The loader **
<div class="rating_loader"><i class="icon-spinner icon-spin icon-large"></i> Chargement...</div>
<script>
var loader = $(".rating_loader");
loader.hide();
$(document).ajaxStart(function() {
loader.fadeIn('slow');
}).ajaxComplete(function() {
loader.hide();
});
</script>
School_controller.rb (the method)
def verified_rating
#school = School.find(params[:school_id])
#ratings = #school.ratings.desc(:created_at)
respond_to do |format|
format.js
end
end
verified_rating.js.erb
$('#verifie').html("<%= escape_javascript render(:partial => 'rating', collection: #ratings.where(:verified => true)) %>");
$('#verified_link').one('click', function(e){
e.preventDefault();
$('#verifie').addClass('active');
$('#non_verifie').removeClass('active');
$('#all_avis').removeClass('active');
});
Is there a way to kill the call after the data are loaded and keep them in the page ?
Thank !

How do I dynamically populate a partial based on a link click?

So I have a partial called _node.html.erb. That is rendered in different views (and from different controllers) throughout my app. On one particular view, what happens is I generate a list of events that are linkable (and unique).
What I want to happen is, whenever someone clicks one of those links, it shows the partial and populates it with the correct info. The way I was initially trying to do it is via passing data-attributes from the link to some JS that does the updating of the partial. This quickly runs into a wall when we reach the node.comments, where the partial should load all of the comments associated with the node being rendered.
For example, here is some sample code of what my _node.html.erb may look like:
<!-- Card Meta -->
<div class="card__meta">
<aside>
<%= image_tag node.user.avatar.url, class: "card__author-avatar", alt: "" %>
</aside>
<section>
<h6 class="card__author-name"><%= node.user.name %></h6>
<time class="card__date"><%= node.created_at.strftime("%B %d, %Y") %></time>
<p class="card__description"><%= node.media.try(:description) %>
<textarea class="card-form__description" name="" id="" cols="30" rows="10">Aenean lacinia bibendum nulla sed consectetur.</textarea>
</section>
</div>
<!-- End Card Meta -->
<!-- Card Comments -->
<div class="card-comments-container">
<h4 class="card-comments__title"><%= pluralize(node.comments_count, "Comment") %></h4>
<a class="card-comments__button"><i class="icon-arrow-down"></i></a>
<div class="card-comments">
<%= render partial: "nodes/comment", collection: node.comments.includes(:user).order(created_at: :desc) %>
</div>
</div>
<!-- End Card Comments -->
Here is a sample of the link that a user can press, or rather the Rails code that generates the link:
<div class="item-wrapper">
<div class="item_comment">
<h5 class="item_comment-title"><%= event.action %> on <%= link_to event.eventable.node.name, "#", data: {toggle_card: "", card_id: event.eventable.node.id, card_title: event.eventable.node.media.title, card_description: event.eventable.node.media.description, card_time: event.eventable.node.media.created_at, card_comments: event.eventable.node.comments_count, card_favorites: event.eventable.node.cached_votes_total } %></h5>
<p class="item_comment-text"><%= event.eventable.message %></p>
</div>
</div>
This is an example of the type of jQuery I was using to do it originally:
$(document).ready(function() {
var overlay = $('.card-overlay'),
$card = overlay.find('.card'),
triggers = $('a[data-toggle-card]');
overlay.hide();
overlay.click(function(e) {
if( e.target != this ) {
} else {
overlay.hide();
}
});
triggers.click(function() {
var trigger = $(this);
var tr = trigger;
overlay.show();
var card = {
id: tr.data('card-id'),
title: tr.data('card-title'),
description: tr.data('card-description'),
time: tr.data('card-time'),
comments: tr.data('card-comments'),
favorites: tr.data('card-favorites'),
};
$card.attr('id', 'card-' + card.id);
$card.find('.card__content').attr('style', 'background-image: url(' + card.image + ')');
$card.find('.card__favorite-count').html('<i class="icon-heart"></i> ' + card.favorites);
$card.find('.card__comment-count').html('<i class="icon-speech-bubble-1"></i> ' + card.comments);
$card.find('.card__title').text(card.title);
$card.find('.card__date').text(card.time);
$card.find('.card__description').text(card.description);
$card.find('textarea').attr('id', 'card-input-field-' + card.id);
var player = videojs("example_video_" + card.id, {}, function() {});
player.src(
{
src: card.video.mp4, type: 'video/mp4'
},
{
src: card.video.webm, type: 'video/webm'
},
{
src: card.video.ogv, type: 'video/ogv'
}
);
player.load();
player.ready(function() {
console.log("READY")
});
return false;
});
});
How do I do this in a more "Railsy" way without relying on too much JS/jQuery?
Edit 1
After attempting Rich Peck's suggestion, I got this error:
05:46:31 web.1 | Unexpected error while processing request: JS, accepted HTTP methods are OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, VERSION-CONTROL, REPORT, CHECKOUT, CHECKIN, UNCHECKOUT, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE-CONTROL, MKACTIVITY, ORDERPATCH, ACL, SEARCH, and PATCH
To be clear, this is what I did:
Modified my routes to have this:
resources :events do
get :node, action: :node
end
Then created an action in my EventsController called node like this:
def node
#node = current_user.nodes.find(params: [node_id])
respond_to do |format|
format.js
end
end
Then I created a app/views/events/node.js.erb, that looks like so:
$overlay = $('.card-overlay');
$node = $('<%= j render "nodes/node", locals: {node: #node} %>');
$overlay.append($node);
Then I modified the link_to tag like so:
<%= link_to event.eventable.node.name, event_node_path(event.eventable.node), remote: true, method: :js %></h5>
Okay there are two problems here:
You're trying to load & populate a partial without any extra data (IE node.comments)
Secondly, you're heavily relying on JQuery to update various attributes on the page. Although there is nothing wrong with this in itself, it does become an issue if you're looking to change page layouts etc.
A more "railsy" way to do this would be to remotely load the node directly from the server, much as Alex Lynham recommended:
#config/routes.rb
resources :cards do
get :node_id, action: :node #-> url.com/cards/:card_id/:node_id
end
#app/controllers/cards_controller.rb
class CardsController < ApplicationController
def node
#card = Card.find params[:card_id]
#node = #card.nodes.find params[:node_id]
respond_to do |format|
format.js #-> app/views/cards/node.js.erb
end
end
end
#app/views/cards/node.js.erb
$node = $("<%=j render "cards/node", locals: {node: #node} %>")
$('body').append($node);
#app/views/cards/index.html.erb
<% #cards.each do |card| %>
<%= link_to card.name, card_node_path(#card), remote: true, format: :js %>
<% end %>
This will keep the code clear and concise.
You can put all your options directly in the _node.js.erb partial. Although it still means there are some changes you have to make, it will give you the ability to change your partial & implementation without all the JQuery specialization you have now.
You need to make an AJAX request to the server, then use a js.erb partial to return what you are looking for to the frontend, probably. That's the most 'Rails-y' way I can think of.
Personally, I'd ask for JSON and then use a bit of jQuery to wrangle the return values much as you have done in your example. By calling out to the backend is the only sane way you'll be able to get the associated records, the .comments bit, which is I believe where you're stuck, if I've understood your question correctly.
EDIT:
This is quite a good tutorial on how AJAX-y stuff can be accomplished with Rails, in case you've not had much cause to do so in the past.

jquery not loading (tokeninput) in Rails app

I'm trying to follow along with the instructions on how to implement jquery-tokeninput here:
How to use jquery-Tokeninput and Acts-as-taggable-on
I am trying to attach tags to a "post". I can use the text field in my form to create new tags. However, the javascript just will not fire at all. It's as if none of the code even exists. I don't get any error messages. The text field just acts as a normal text field with no javascript events firing. What can be the issue? Here is my relevant code:
post model
attr_accessible :tag_list
acts_as_taggable_on :tags
posts controller
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
Tag.find_or_create_by_name(query)
end
#Do the search in memory for better performance
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json{ render :json => #tags.map(&:attributes) }
end
end
routes
# It has to find the tags.json or in my case /products/tags.json
get "posts/tags" => "posts#tags", :as => :tags
application.js
$(function() {
$("#post_tags").tokenInput("/posts/tags.json", {
prePopulate: $("#post_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
});
Out of frustration I also created a posts.js.coffee file just to see if that was the problem:
$ ->
$("#post_tags").tokenInput "/posts/tags.json",
prePopulate: $("#post_tags").data("pre")
preventDuplicates: true
noResultsText: "No results, needs to be created."
animateDropdown: false
post form view
<%= f.label :tag_list %>
<%= f.text_field :tag_list, :id => "post_tags", "data-pre" => #post.tags.map(&:attributes).to_json %>
Am I missing something here? Any help would be greatly appreciated!!!
Thanks!!

Categories