i'd like to make a like/dislike ability on my RoR application. How can i make it via Ajax-requests ?
dislike and like - are integer how can i make an Ajax-request, than i can send the data of what i want to increment either "like" or "dislike" counter in my methods
I have a table with posts :
#app/views/dashboard/view.html.erb
<table>
<%if #post.count!=0%>
<%#post.each do |p|%>
<%if !p.text.nil?%>
<tr>
<td><b class="margin"><h4><%=p.text%></b></h4></td>
<td>by <%=p.user.username%> </td>
<td><span class="glyphicon glyphicon-thumbs-up likeAction"><%= link_to p.like, dashboard_like_path, :remote => true, :id => 'likecount' %> </td>
<td><span class="glyphicon glyphicon-thumbs-down"><%= link_to p.dislike, dashboard_dislike_path, :remote => true, :id => 'dislikecount' %> </td>
<%end%>
<% end %>
<%else%>
There's no posts yet, but you can add <%=link_to "one", dashboard_posts_create_a_post_path%>
<%end%>
</table>
My js file
#app/views/dashboard/view.js
$('#likecount').text(#post.like);
$('#dislikecount').text(#post.dislike);
my methods in controller :
#app/controller/dahsboard_controller.rb
def like
#post.increment!(:like)
respond_to do |format|
format.html
format.js
end
end
def dislike
#post.increment!(:dislike)
respond_to do |format|
format.html
format.js
end
end
My dashboard.js in assets/javascripts
jQuery(function($) {
$("likeAction").click(function(){
$.ajax({
url: dashboard_like_path,
type: 'POST',
success: function(){
$('#linkcount').text(data);
}
error: function(error){
alert(error);
}
});
});
});
You already have Rails built-in AJAX functionality, so no need for calling $.ajax. Simply set remote: true on your link_to 'Like', ..., remote: true and respond with the same code you have in app/views/dashboard/view.js: format.js { render action: 'view' }
EDIT: As long as like and dislike are set as member routes on posts:
dislike_post POST /posts/:id/dislike(.:format) posts#dislike
like_post POST /posts/:id/like(.:format) posts#like
You will have a params[:id] (if you send one) to do something like #post = Post.find(params[:id]), if you share this code with show, like and dislike. You can create a set_post before filter, so you don't repeat yourself.
You'll probably want to look at a gem called acts_as_votable
This sets much of your model functionality up - allowing you to use the likes of #post.downvote_from #user2 etc. I'll let you look into that, as it's what you need in the backend I think.
In regards the front-end (especially Ajax), you'll have to set up a controller action, and then hit it with a JS request:
#config/routes.rb
resources :posts do
match :vote, via: [:post,:delete]
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
respond_to :js, :html, only: :vote
def vote
if request.delete?
#downvote
elsif request.post?
#upvote
end
end
end
This will allow you to use the following:
#app/views/posts/vote.js.erb
$(".element").html("<%=j render partial: "post/vote_count", object: #post %>");
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= render partial: "post/vote_count", object: :post %>
<% end %>
#app/views/posts/_vote_count.html.erb
<% method = #post.liked_by(current_user)
<%= link_to post.likes, post_vote_path(post), method: :post, remote: true %>
--
The Ajax functionality is pre-built into Rails; you have to be wary of which controller action it's going to send you to, as well as the response given.
My above code uses the respond_to block to invoke the .js.erb response -- allowing you to perform some actions when you send your request.
Related
I'm working on an dynamic edit of an article using rails. On each articles can be added paragraphs. I wanted to use Ajax with Jquery following the 136-jquery-ajax-revised tutorial from railscast. I created the template for the form and the new.js.erb response but I keep geting same error:
ParagraphsController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []
This is the link that request the new form from view/article/show
<%= link_to 'New Paragraph', new_article_paragraph_path(#article), remote: true, class: 'uk-button uk-button-primary' %>
view/paragraph/_form
<div class="article-form-container">
<%= form_with scope: :paragraph, url: article_paragraphs, remote: true do |f| %>
<%= f.text_field :title, class: 'uk-input', placeholder: 'Title of paragraph (optional, can be a subtitle of the article)' %>
<%= f.text_area :text, class: 'uk-textarea', placeholder: 'Content of paragraph' %>
<%= f.hidden_field :position, :value => 3 %>
<div class="submit-button">
<%= f.submit class: 'uk-button uk-button-primary' %>
</div>
<% end %>
</div>
routes
resources :articles, only: %i[show update destroy create]
resources :articles do
resources :paragraphs, only: %i[new create update destroy]
end
view/paragraphs/new.js.erb
$('div#article-control-panel').hide("fast", function () {
$('div#article-control-panel').after('<%= j render 'form' %>')
})
controllers/paragraphs_controller
class ParagraphsController < ApplicationController
def new
#paragraph = Paragraph.new
end
def create
#paragraph = Paragraph.new(paragraph_params)
#article = Article.find(params[:article_id])
if #article.user == current_user
#paragraph.article = #article
#paragraph.save
end
respond_to do |format|
format.html { redirect_to #article }
format.js
end
end
def paragraph_params
params.require(:paragraph).permit(:title, :text, :position)
end
end
Can't figure out what the problem is. The error happens in the article page, after I press the link button.
Edit
Strange thing is that if i try to change the url of orm in something like article_path it will work...
Your controller is missing a respond_to. Your new function should look like this:
def new
#paragraph = Paragraph.new
respond_to { |format| format.js }
end
This respond_to allows rails to call the relevant .js.erb file, in this instance your new.js.erb file.
Just keep in mind that when you want to perform an ajax call, you require these few elements:
A respond_to { |format| format.js } in your controller action
Your js.erb file needs to be the same name as your controller action (foo.js.erb)
A partial to render through your js.erb file, typically named the same as your js file. (_foo.js.erb)
If it is a link, you need remote: true. If it is a form and you're using rails 5, you could use form_with or simply include remote: true too.
my route.rb
post 'home/create'
get 'home/create'
my HomeController
def create
#review_n = Review.create(review_params)
if #review_n.errors.empty?
respond_to do |format|
format.js { render 'create', locals: {review_name: #review_n.review_n, review_body: #review_n.review_body} }
end
else
render 'index'
end
end
my create.js.erb
$(function() {
$(".wrap-body").append("<div> tmp </div>");
});
rails say: ActionController::UnknownFormat in HomeController#create
I want send data in my html.erb without reload page. Help me, please!
UPD:
my html.rb
<%= form_tag home_create_path, :method => 'post', :remote => true do %>
<%= text_area_tag 'review[review_body]', nil %>
<%= text_field_tag 'review[review_name]', nil %>
<%= submit_tag 'send' %>
<% end %>
Is your code hitting the render 'index' call that is outside of the respond_to block?
Normally you would put all render calls inside the respond_to block, so it's obvious that all paths of your logic can respond to all expected formats:
def create
#review_n = Review.create(review_params)
respond_to do |format|
if #review_n.errors.empty?
format.js { render 'create', locals: {review_name: #review_n.review_n, review_body: #review_n.review_body} }
else
format.js { render 'index' }
end
end
end
This requires having both a create.js.erb and an index.js.erb (for the error case).
Also, as #sahil recommended, your routes.rb should not declare get 'home/create' - this action modifies data, so it isn't safe to make it accessible via GET.
I'm having trouble passing locals to a partial shared by three different views, each related to different actions in different controllers. The partial and the locals passed to it work without a problem when working with html requests, but I cannot get them to work when issuing xhr requests.
Let me show you my code to explain myself better.
So, I have this partial
# app/views/shared/_vote_form.html.erb
<div id="vote_form">
<% if post.votes.find_by(user_id: current_user.id).nil? %>
<%= render partial: "votes/vote", locals: { postv: post,
vote: post.votes.build } %>
<% else %>
<%= render partial: "votes/unvote", locals: { postu: post,
vote: post.votes.find_by(user_id: current_user.id) } %>
<% end %>
</div>
As you can see, this partial renders one of two partials depending on the outcome of an if statement:
# app/views/votes/_vote.html.erb
<%= form_for([postv, vote], remote: true) do |f| %>
<%= hidden_field_tag 'vote[vote]', 1 %>
<%= f.submit "Vote", class: "btn" %>
<% end %>
# app/views/votes/_unvote.html.erb
<%= form_for([postu, vote], html: { method: :delete }, remote: true) do |f| %>
<%= f.submit "Unvote", class: "btn btn-primary" %>
<% end %>
As I mentioned, this partials are shared by three different views associated to different actions in different controllers.
# app/views/posts/show.html.erb
<div class="post-vote-form">
<%= render partial: "shared/vote_form", locals: { post: #post } %>
</div>
Which is associated to the following action in the following controller:
class PostsController < ApplicationController
def show
#post = Post.find(params[:id])
end
end
Another view
# app/views/users/feed.html.erb
<% #feed_items.each do |f| %>
<div class="vote-button">
<%= render partial: "shared/vote_form", locals: { post: f } %>
</div>
<% end %>
Associated to the following controller#action
class UsersController < ApplicationController
def feed
#user = User.find_by(id: current_user.id)
#feed_items = #user.feed
end
end
And finally
# app/views/categories/other.html.erb
<% #gal_items.each do |f| %>
...
<%= render partial: "shared/vote_form", locals: { post: f } %>
<% end %>
Associated to controller#action
class CategoriesController < ApplicationController
def other
#other = Category.find(7)
#gal_items = #other.posts
end
end
As you can see, the forms send an xhr request to create/destroy an instance of Vote (I have routes for Vote nested in Post. That's why the form_for takes two arguments).
These requests are handled by the following actions in the VotesController
class VotesController < ApplicationController
def create
#post = Post.find_by(id: params[:post_id])
#vote = #post.votes.build(vote_params)
#vote.user_id = current_user.id
#vote.save
respond_to do |format|
format.html { redirect_to #post }
format.js
end
end
def destroy
#vote.destroy
respond_to do |format|
format.html { redirect_to #post }
format.js
end
end
end
And these two js.erb files come into play:
# app/views/votes/create.js.erb
$("#vote_form").html("<%= escape_javascript(render :partial => 'votes/unvote', locals: { postu: #post,
vote: #post.votes.find_by(user_id: current_user.id)}) %>");
And
# app/views/votes/destroy.js.erb
$("#vote_form").html("<%= escape_javascript(render :partial => 'votes/vote', locals: { postv: #post.each,
vote: #post.votes.build }) %>");
The way I am presenting these last two js.erb files work for the view # app/views/posts/show.html.erb as the values for the locals are taken directly from the VotesController actions, but I have not been able to find a way to make it work for the other two views (which are #something.each do |f|) that render these partials, as I cannot pass the appropriate values to the locals for the form_for arguments to work.
I have tried with a helper to pass values to the locals depending on the url, but without success.
It seems obvious that I cannot get these js.erb files to render the partials with appropriate values for the locals because I cannot retrieve the variables from their respective controllers.
So, bottomline, is there a way to make it work through these js.erb files, or will I have to sort this out using pure JQuery?
Has anyone faced something like this?
I am sorry that I cannot make a question that requires a more specific answer.
Hope you guys can help.
Did you try this?
class UsersController < ApplicationController
def feed
#post = Post.find(params[:id])
#user = User.find_by(id: current_user.id)
#feed_items = #user.feed
end
end
class CategoriesController < ApplicationController
def other
#post = Post.find(params[:id])
#other = Category.find(7)
#gal_items = #other.posts
end
end
I have OrdersController show action (with order_info partial), which displays current status of the order ("paid", "canceled", etc.).
meanwhile, I have callback action order_callback, which executes update action and changes status of the order in the database when it receives the callback from a payment processor.
What I want to achieve is to update show action in real-time to capture changes in order status (e.g. order paid successfully).
I tried to use unobtrusive javascript, but did not succeed.
update.js.erb
$("#order").html("<%= escape_javascript(render 'order_info') %>")
show.html.erb
<div id="order">
<%= render 'order_info' %>
</div>
orders_controller.rb
def update
if #order.update_attributes(order_params)
flash[:success] = "Order updated."
redirect_to #order
else
render 'edit'
end
end
api/orders_controller.rb
def order_callback
signature = request.headers['X-Signature']
request_uri = URI(env['REQUEST_URI']).request_uri rescue env['REQUEST_URI']
if $processor.callback_valid?(signature, request_uri)
#order = Order.find(params["id"])
#order.update_attributes(status: params["status"])
render status: :ok,
json: { success: true,
info: "Successfully updated order." }
else
render status: :unprocessable_entity,
json: { success: false }
end
end
I am using rails 4.2.2 with turbolinks enabled.
I was able to resolve it with javascript polling. The critical line was to explicitly say which .js partial to render in respond_to block
show.js.erb
$("#order").html("<%= j render 'orders/order_info', locals: {order: #order} %>");
OrderPoller.poll();
orders_controller.rb
def show
#order = Order.find(params[:id])
respond_to do |format|
format.js { render "orders/show.js.erb" }
format.html
end
end
orders.coffee
#OrderPoller =
poll: ->
setInterval #request, 5000
request: ->
$.get($('#order').data('url'))
jQuery ->
if $('#order').length > 0
OrderPoller.poll()
show.html.erb
<%= content_tag :div, id: "order", data: { url: order_path(#order) } do %>
<%= render 'order_info' %>
<% end %>
I am trying to upload files using a Rails form where the remote is set to true. I'm using Rails 4.1.1. Let's say that my model is a Message, and it is using JavaScript so that the user could easily send multiple messages without reloading the page. The form is set like this:
<%= form_for #message, url: {action: "create"}, html: {:class => "message-form", multipart: true}, remote: true do |f| %>
The user can upload images with the Message, if they wish to do so. MessageImage acts as a nested attribute in the form, and is declared like this (http://railscasts.com/episodes/196-nested-model-form-revised way):
<%= f.fields_for :message_images do |builder| %>
<%= render 'message_image_fields', f: builder %>
<%= link_to_add_fields "Add an image", f, :message_images %>
<% end %>
On my controller the action is roughly like this:
if #message.save
flash.now[:success] = "Message sent"
else
flash.now[:alert] = "Error sending the message"
end
respond_to do |format|
format.html { render 'new' }
format.js { render 'new' }
end
Now, this works perfectly as long as the user doesn't send any images, but if they do, it uses format.html instead of format.js. Removing the format.html gives ActionController::UnknownFormat-exception.
Now, this obviously has to do with the fact that you can't submit files with remote set to true. I tried searching a bit, and found this gem https://github.com/JangoSteve/remotipart , which seems to be exactly what I'm looking for. I installed it following the instructions, but for some reason it still doesn't work and gives ActionController::UnknownFormat-exception if I remove the format.html. However, I couldn't find any example of it involving nested attributes. Are there any alternatives for this gem or any other way to fix this, or should I just set that it renders HTML if the user submits files?
JQuery
I don't know how to get the nested model aspect of this, but we've done file uploading with JQuery / asynchronicity before here (register for account, log into profile):
We used the jquery-file-upload gem - basically allowing you to pass the files through Ajax to your controller backend. To give you a clear idea of how we did this:
--
Code
#app/assets/javascripts/application.js
$('#avatar').fileupload({
url: '/profile/' + $(this).attr('data_id'),
dataType: 'json',
type: 'post',
add: function (e, data) {
$(this).avatar_loading('avatar_loading');
data.submit();
},
success: function (data, status) {;
$("#avatar_img").fadeOut('fast', function() {
$(this).attr("src", data.avatar_url).fadeIn('fast', function(){
$(this).avatar_loading('avatar_loading');
});
});
}
});
#app/views/users/index.html.erb
<%= form_for :upload, :html => {:multipart => true, :id => "avatar"}, :method => :put, url: profile_path(current_user.id), "data_id" => current_user.id do |f| %>
<div class="btn btn-success fileinput-button avatar" id="avatar_container">
<%= f.file_field :avatar, :title => "Upload New" %>
<%= image_tag(#user.profile.avatar.url, :width=> '100%', :id => "avatar_img", :alt => name?(#user)) %>
</div>
<% end %>
#app/controllers/profile_controller.rb
Class ProfileController < ApplicationController
def update
def update
#profile = User.find(current_user.id)
#profile.profile.update(upload_params)
respond_to do |format|
format.html { render :nothing => true }
format.js { render :partial => 'profiles/update.js' }
format.json {
render :json => #profile.profile.as_json(:only => [:id, :avatar], :methods => [:avatar_url])
}
end
def upload_params
params.require(:upload).permit(:avatar, :public, :description)
end
end
end
--
Implementation
For your implementation, I would recommend firstly creating the message, and then getting the user to append some images to it in another action
After you've got that working, you could get it to work as one form