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

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.

Related

How to display and link an certain attribute of an object

I'm trying to display all the topics that are being discussed on my application, so I created a dropdown menu (btw if there is a better way of doing this please feel free to share it):
<div class="form-group">
<label for="topic_category">Category</label>
<select id="topic_category" name="topic[category]" class="form-control">
<option>Art</option>
<option>Business</option>
<option>Books</option>
<option>Charity</option>
<option>Coding</option>
<option>Cooking</option>
<option>Dance</option>
<option>Design</option>
<option>DIY</option>
<option>Engineering</option>
<option>Fashion</option> etc...
</select>
</div>
What I'm trying to do is to create links to each single category and also display how many topics are active through something like
----- Edit 1 -----
what I'm trying to achieve is something like this:
<div class="row">
<div class="col-md-6" style ="padding-top: 10px; border-right: 1px solid #ccc;">
<h4 style="text-align: center;">Topics</h4>
<link_to project.category(Art)> (project.category(art).count)
</div>
I know that is wrong but is the closest I get to explaining what I'm trying to achieve
---- Edit 2 -----
So I'm still trying to get it right, this is probably because I'm a newbie. So following your answers I implemented the code which looks something like this.
static_pages controller.
def home
if logged_in?
#user = current_user
#topics_count = Topic.group(:category).count
end
end
def categoryselection
category = params[:category]
topics = Topic.where(category: category)
render json: { success: true, Topics: topics }
end
def help
end
def about
end
def contact
end
end
home view
.......
<% if logged_in? %>
<div class="row">
<%= render 'shared/sidebar_right' %>
<div class="col-md-8" style = "align-content: right">
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.....
Sidebar_right view
......
<div id='topics'>
<% #topics_count.each do |topic, count| %>
<a class ='project-link' href='topic_path?category=<%= topic %>'> <%= topic %> (<%= count %>)</a>
<% end %>
......
<script type>
$('#topics').on('change', '.topic-link' function (e) {
e.preventDefault();
var category = $(e.currentTarget).val();
var queryUrl = $(e).href + '?category=' + category;
$.get(queryUrl, function(resp) {
if (resp.success) {redirect_to topic_path
// select the div or whatever node on the DOM you are displaying the result, and change it.
}
});
});
</script>
Topics Controller
class TopicsController < ApplicationController
.....
def index
end
def show
#user = current_user
#topic = Topic.find(params[:id])
end
.....
The error I'm getting is no routes match [GET] topic_path, and I've checked on my routes and it does exit it actually refers to "show", is this happening because the page I'm using is home rather than the topics page?
Thanks!
Based on your new edit
In the controller:
def index
#topics_count = Topic.group(:category).count
# this will give you something like this { 'Art' => 10, 'Business' => 15' }
end
on the html page:
<div id='topics'>
<% #topics_count.each do |topic, count| %>
<a class='topic-link' href='topic_show_url?category=<%= topic %>'> <%= topic %> (<%= count %>)</a>
<% end %>
</div>
You don't need to (and shouldn't) load all the topics because you are just showing the count, and at large scale, loading the entire table will likely crash your site.
This will take care of your second request, showing the topic the user picks when the link is clicked.
on the page, send ajax request when user select one of those options.
$('#topics').on('click', '.topic-link', function (e) {
e.preventDefault();
var category = $(e.currentTarget).val();
var queryUrl = $(e).href + '?category=' + category;
$.get(queryUrl, function(resp) {
if (resp.success) {
// select the div or whatever node on the DOM you are displaying the result, and change it.
}
});
});
in the controller
def you_define_this_method
category = params[:category]
topics = Topic.where(category: category)
render json: { success: true, topics: topics }
end
You can add a link inside your <option> tag... ie.
<option>Art</option>

Acts As Votable Ajax Vote Size Not Updating Rails

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).

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!!

Backbone.js - Rendering array from View without making a loop in the template

I'm loading all posts for a page using backbone. And Loading comments for a post when clicked on the "Get all comments" link. I'm getting all comments from an Ajax call.
Social.Views.StreamsIndex = Backbone.View.extend({
comment_template: JST['streams/comment'],
comment_displayall: function(data, post_id) {
this.$("#comments").html(this.comment_template({
comment: data // Here data is array
}));
}
});
I have comment.jst.ejs file which has a loop now but I have to put it in view
<% _.each(comment.comments,function(comment){ %> // I want to get rid of this Line
<div class="stream_comment">
<div class="stream_commentphoto">
<a><img src="<%= comment.actor.thumbnail.url %>"></a>
</div>
<div class="stream_comm-content">
<h5><%= comment.actor.displayName %></h5>
<p><%= comment.content %></p>
</div>
</div>
<%}%>
How can i get rid of the loop inside Comment template, by adding the loop in view?
Perhaps something like this:
comment_displayall: function(data, post_id) {
//clear any existing comments
var $comments = this.$("#comments").empty();
//render each comment separately and append to the view
_.each(data.comments, function(comment) {
$comments.append(this.comment_template({comment:comment});
}, this);
}
And simply remove the loop construct (first and last line) of your template.
/Code sample not tested

how to use ajax with json in ruby on rails

I am implemeting a facebook application in rails using facebooker plugin, therefore it is very important to use this architecture if i want to update multiple DOM in my page.
if my code works in a regular rails application it would work in my facebook application.
i am trying to use ajax to let the user know that the comment was sent, and update the comments bloc.
migration:
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.string :body
t.timestamps
end
end
def self.down
drop_table :comments
end
end
controller:
class CommentsController < ApplicationController
def index
#comments=Comment.all
end
def create
#comment=Comment.create(params[:comment])
if request.xhr?
#comments=Comment.all
render :json=>{:ids_to_update=>[:all_comments,:form_message],
:all_comments=>render_to_string(:partial=>"comments" ),
:form_message=>"Your comment has been added." }
else
redirect_to comments_url
end
end
end
view:
<script>
function update_count(str,message_id) {
len=str.length;
if (len < 200) {
$(message_id).innerHTML="<span style='color: green'>"+
(200-len)+" remaining</span>";
} else {
$(message_id).innerHTML="<span style='color: red'>"+
"Comment too long. Only 200 characters allowed.</span>";
}
}
function update_multiple(json) {
for( var i=0; i<json["ids_to_update"].length; i++ ) {
id=json["ids_to_update"][i];
$(id).innerHTML=json[id];
}
}
</script>
<div id="all_comments" >
<%= render :partial=>"comments/comments" %>
</div>
Talk some trash: <br />
<% remote_form_for Comment.new,
:url=>comments_url,
:success=>"update_multiple(request)" do |f|%>
<%= f.text_area :body,
:onchange=>"update_count(this.getValue(),'remaining');" ,
:onkeyup=>"update_count(this.getValue(),'remaining');"
%> <br />
<%= f.submit 'Post'%>
<% end %>
<p id="remaining" > </p>
<p id="form_message" > </p>
<br><br>
<br>
if i try to do alert(json) in the first line of the update_multiple function , i got an [object Object].
if i try to do alert(json["ids_to_update"][0]) in the first line of the update_multiple function , there is no dialog box displayed.
however the comment got saved but nothing is updated.
questions:
1.how can javascript and rails know that i am dealing with json objects?deos ROR sent it a object format or a text format?
2.how can i see what is the returned json?do i have to parse it?how?
2.how can i debug this problem?
3.how can i get it to work?
You have to parse the returned JSON in your javascript using a JSON parser.
Here is the one I use: https://github.com/douglascrockford/JSON-js/blob/master/json2.js
So upon success you'll do something like:
var stuff = json.parse(returnedJSON)

Categories