I'm sure this question has been asked before in a different context but I'm still so stuck with figuring out AJAX Rails and, I guess, Rails in general (kinda makes me wonder if I should just go back to PHP...). Well anyways I have this form that I want to AJAXify.
This is the "list" view which is part of the "subject" controller
<h1>Listing Subjects</h1>
<ul id="subject_list">
<% #subjects.each do |c| %>
<li><%= link_to c.name, :action => 'show', :id => c.id %></li>
<% end %>
</ul>
<p id="add_link"><%= link_to_function("Add a Subject",
"Element.remove('add_link'); Element.show('add_subject')")%></p>
<div id="add_subject" style="display:none;">
<%= form_tag(:action => 'create') do%>
Name: <%= text_field "subject", "name" %>
<%= submit_tag 'Add' %>
<% end %>
</div>
Code for my "subject" controller
class SubjectController < ApplicationController
def list
#subjects = User.find(:all)
end
def show
#subject = User.find(params[:id])
end
def create
#subject = User.new(params[:subject])
if #subject.save
render :partial => 'subject', :object => #subject
end
end
end
My "subject" partial
<li id="subject_<%= subject.id %>">
<%= link_to subject.name, :action => 'show', :id => subject.id %>
</li>
And the User is just a simple model I made that contains two columns "name" and "email".
How this code currently works is that when you click "Add", the textfield input is revealed. When you type something in the input and submit it, the "_show" partial is rendered in the create link. I was following a Rails 2.0 tutorial but I have 3.0 and I've read through some tutorials and they all mention ":remote => true" and jquery_ujs.js but I have no idea how to apply them to a "form_tag" rather than "form_for" Rails helper.
Basically I want to asynchronously add the element to the bottom of the list without a page load. I've really tried to understand absolutely all of the tutorials I could find but I just can't figure it out.
I believe that you'd better use some Unobtrusive JavaScript to tell to your app
and browser what exactly you want to render and how.
You want too much from simple render :partial => 'subject', :object => #subject line of code.
Here's my snippet that may be helpful to you.
# in the view (:remote => true for form_tag is not problem at all)
<%= form_tag({:controller => :products, :action => :empty_cart }, {:id => 'empty_cart', :remote => true}) do %>
<%= submit_tag 'Clear' %>
<% end %>
# in the controller (note that format.js section in the respond_to block)
def empty_cart
...
respond_to do |format|
format.html { redirect_to :root, :notice => 'Your cart is empty now' } # in the case of disabled JS support
format.js { render :js => "$('#empty_cart').fadeOut()" } # or you can place js code in the empty_cart.js.erb file and specify format.js here without the block
end
end
Check this article if I'm not clear enough.
Related
In my rails app I have a model Song. On one of the user's profile pages user_music_path(#user) I'm rendering all of their songs.
People can also like these songs, which is set up and working, I just want to use AJAX instead of page refreshes. The problem I'm having is when using ajax, I get the error undefined local variable or method "song" when I'm clicking the link "like".
Here is some of my code:
User's Music Page views/users/music.html.erb
...
<ul class="playlist list-group">
<%= render #songs %>
</ul>
views/songs/_song.html.erb
<li class="list-group-item">
<p class="pull-right">
<%= render :partial => 'songs/like_button', :locals => {:song => song} %>
<%= render :partial => 'songs/likes', :locals => {:song => song} %>
</p>
</li>
views/songs/_like_button.html.erb
<% if current_user.voted_on?(song) %>
<%= link_to "Unlike", unlike_song_path(song), :method => :post, :remote => true %>
<% else %>
<%= link_to "Like", like_song_path(song), :method => :post, :remote => true %>
<% end %>
views/songs/_likes.html.erb
<%= song.votes.count %>
views/songs/like.js.coffee
$("p.pull-right").html('<%= render :partial => "songs/like_button", :locals => {:song => song} %><%= render :partial => "songs/likes", :locals => {:song => song} %>');
controllers/songs_controller.rb
def like
begin
#vote = current_user.vote_for(#song = Song.find(params[:id]))
#vote.save
respond_with #song.user, :location => user_music_path(#song.user)
rescue ActiveRecord::RecordInvalid
redirect_to #song
end
end
And as mentioned earlier, the :like action worked perfectly before I starting using AJAX. I've also added respond_to :html, :js to my songs_controller. And the error I get when I try to like the song is ActionView::Template::Error (undefined local variable or method "song" for #<#<Class:0x000001028de9d0>:0x00000106ecd9f0>):
Where you have
<%= song.name %>
try instead
<%= #song.name %>
And do the same with your link_to paths and your render calls.
Do rake routes and use of of them for the link, e.g. (pseudo-code) link_to 'song', song_audio_path
I've built a fav icon that works like a Facebook like/unlike button, and I'm trying to update it via Ajax but can't seem to get it working. I think I'm missing something simple.
events_controller.rb
def fav
#event = Event.find(params[:id])
current_user.toggle_flag(#event, :fav) #events_helper.rb
respond_to do |format|
format.js
end
end
events_helper.rb
def toggle_fav(event, user)
if user_signed_in? #change icon from heart to empty heart and vice-versa
link_to user.flagged?(event, :fav) ? #if the event is flagged
content_tag(:span, " ", :class => "glyphicon glyphicon-heart") : #show full heart
content_tag(:span, " ", :class => "glyphicon glyphicon-heart-empty"), #else show empty heart
fav_event_path(event), #path that changes the state of the heart
:remote => true
else
link_to content_tag(:span, " ", :class => "glyphicon glyphicon-heart-empty"), fav_event_path(event) #prompt user to sign in
end
end
Events/index.html.erb
<% #events.each do |event| %>
<%= render 'each_event', :event => event %>
<% end %>
_each_event.html.erb (relevant info only)
<div class="row">
<div class="event-div">
<div class="event-details">
<ul><li class="fav-li"><%= render 'fav_li', :event => event %></li></ul>
</div>
</div>
</div>
_fav_li.html.erb
<%= toggle_fav(event, current_user) %>
fav.js.erb
$('.fav-li').html('<%=j render 'events/fav_li', :event => event, :layout => false %>');
I followed this tutorial to make the like button: http://www.youtube.com/watch?v=GG-kCSx0taU
And am using the make_flaggable gem.
Right now when I update the button it directs me to http://localhost:3000/events/23/fav but that template doesn't exist (and shouldn't). The events are displayed on the event index page after going through the index filters. There are multiple events on a single page.
I'd appreciate any help! Thanks.
Your controller is doing format.js in the respond_to block. It's expecting a js template to exist so it can run it as the response. Something like app/views/events/fav.js.erb. This file will contain some js code that will update your view with the results of your controller action e.g. update the icon from glyphicon-heart-empty to glyphicon-heart.
Here's another question/answer discussing js.erb templates: How does js.erb work
Hope that helps.
I have a rails app, which is constructed by three parts such as navigation above, main content, and side menu.
I already implemented follow un-follow button in my main content.
It's working with Ajax perfectly so far.
basically if I press follow button, follow action in users_controller.rb will be called and changes follow flag, and then it calls follow.js to re-render follow button as partial.
Beside that, in side menu, it has the number of people whom current_user is following.
This number also should be refreshed right after follow action was executed.
To refresh more than 2 partials at once. How can I archive this?
controllers/users_controller.rb
....
def follow
#user = User.find(params[:id])
current_user.follow(#user)
respond_to do |format|
format.js {render :action=>"follow.js"}
end
end
def unfollow
#user = User.find(params[:id])
current_user.stop_following(#user)
respond_to do |format|
format.js {render :action=>"unfollow.js"}
end
end
...
views/users/follow.js.erb
$('.follow_user[data-user-id="<%=#user.id%>"]').html('<%= escape_javascript(render :partial => "users/follow_user", :locals => {:user => #user}) %>');
views/users/unfollow.js.erb
$('.follow_user[data-user-id="<%=#user.id%>"]').html('<%= escape_javascript(render :partial => "users/follow_user", :locals => {:user => #user}) %>');
views/layouts/_menu.html.erb
<% if user_signed_in? %>
<li><%= link_to(following_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Following
<%= '(' + current_user.all_following.count.to_s + ')' if current_user.all_following.count > 0 %>
<% end %>
</li>
<li><%= link_to(followed_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Followed by
<%= '(' + current_user.followers.count.to_s + ')' if current_user.followers.count > 0 %>
<% end %>
</li>
<% else %>
<li><%= link_to sanitize('<i class="icon-heart"></i> ') + "Following", following_user_path %></li>
<li><%= link_to sanitize('<i class="icon-heart"></i> ') + "Followed by", followed_user_path %></li>
<% end %>
# dedicate partial _following_number.html.erb
<span id="following_number">
<% if user.followers.count > 0 %>
<%= '(' + user.followers.count.to_s + ')' %>
<% end %>
</span>
# update layout with partial:
<li><%= link_to(followed_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Followed by
<%= render :partial => "following_number", :user => current_user %>
<% end %>
# follow.js.erb && unfollow.js.erb:
...
$(document).find("#following_number").replaceWith('<%= escape_javascript(render("following_number", :user => #user, :formats => [:html])) %>')
More elegant with helper:
def following_number(user)
user.followers.count > 0 ? "(#{user.followers.count})" : nil
end
# then put spans to layout:
<span id="following_number"><%= following_number(current_user) %></span>
# and js:
$(document).find("#following_number").html('<%= escape_javascript(following_number(#user)) %>')
You could create a new action that render_to_string both of those partials and format them into a json object:
{
div_id_1: "<ul....." ,
div_id_2: "<div...."
}
One simple way of doing this is to return a .js from rails and have your view execute this script, which will have the actions to update/replace your view.
For example this simply renders three partials and later using jQuery replaces the content in your view. Lets say the action is my_action so you will place the following code in your my_action.js.erb:
$("#content_1").html("<%= escape_javascript(render('partial_1'))%>");
$("#content_2").html("<%= escape_javascript(render('partial_2'))%>");
$("#content_3").html("<%= escape_javascript(render('partial_3'))%>");
This example can be extended the way you want or need. You can also render json for your models and call a function to update them in the view.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Adding tags to posts in Ruby on Rails
I have a simple yet tricky (for me at least) question... what would be the best way to create tags in my sample application blog?
I am adding tags using act-as-taggable, but can I make them clickable so that when people click on it, all posts with that tag would be shown?
I can't quite get it O___o
Any help is super appreciated!
Here is what i did so far:
in my posts controller
def tagged
#posts = Post.all(:order => 'created_at DESC')
#tags = Post.tag_counts_on(:tags)
#tagged_posts = Post.tagged_with(params[:tags])
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #posts }
end
end
then in my posts/show view
<% unless #post.tags.empty? %>
<div class="category">Category:
<% #post.tags.each do |t| %>
<%= link_to t.name, {:tag => t.name, :action => "tagged", :controller => 'posts'} %>
<% end %>
in my posts/tagged view
<% #tagged_posts.each do |post| %>
<div class="entry">
<h2><%= link_to post.title, post %></h2>
<div class="content"><%= sanitize blog_truncate(post.content, :words => 100),:tags => %w(strong, b, a) %><br /><%= link_to "[read more]", post %>
</div>
</div>
<% end %>
I kind of loosely followed this guide:
http://g-p.si/posts/tagging-with-acts-as-taggable-on
my issue is that the tag is clickable on my posts/show page, I get redirected to my tagged page and the url looks like mysite/tagged?tag=ruby
But my tagged page is blank...
Each link for the tag that the user clicks on should have a href of something like:
/posts?tag=my_tag_name
And then in the posts controller
class PostsController
def index
if params[:tag].present?
#posts = Post.where(tag: params[:tag])
else
#posts = Post.all
end
end
end
Note this code is not tested and I've never used acts as taggable so you should first make sure how to query for tagged posts.
It's almost right, thanks for the hint! you have to use
#posts = Post.tagged_with(params[:tag])
instead of
#posts = Post.where(tag: params[:tag])
and it works like magic! :)
I'm implementing show/hide feature for users comments.
Discussed here: https://stackoverflow.com/a/10174194/439688
My aim was to:
1. Limit the default shown comments to 2.
2. Have a span with text that states the number of total comments for that particular micropost and when clicked by a user have it expand and show all comments for that micropost. I would be using Jquery/Ajax to hide, show, prepend etc.
The first change was to limit the amount of comments shown to the user and I achieved this by creating a method in my helper called "comments" and here I pass in the id of the micropost the comment belongs to.
def get_comments(micropost_id)
Comment.limit(2).order("created_at DESC").where(:micropost_id => micropost_id)
end
Now the each loop that loops through each comment will only show the 2 most recent comments.
<<% #microposts.each do |m| %>
<% if m.poster_id.nil? %>
<div class="postHolder">
<nav class="micropostOptions">
<ul class="postMenu">
<li class="deletePost"><%= link_to content_tag(:span, "Delete post"), m, :method => :delete, :confirm => "Are you sure?", :title => m.content, :class => "message_delete", :remote => true %>
</li>
<li class="disableCommenting"><%= link_to content_tag(:span, "Pause commenting"), "2" %></li>
<li class="blockCommenter"><%= link_to content_tag(:span, "Block commenter"), "3" %></li>
<li class="openInNewWindow"><%= link_to content_tag(:span, "Open in new window"), "4" %></li>
<li class="reportAbuse"><%= link_to content_tag(:span, "Report abuse"), "5" %></li>
</ul>
</nav>
<%= link_to image_tag(default_photo_for_current_user, :class => "poster_photo"), current_users_username %>
<div class="post_content">
<div class="post_container">
<div class="mainUserNameFontStyle"><%= link_to current_users_username.capitalize, current_users_username %> - <div class="post_time"> <%= time_ago_in_words(m.created_at) %> ago.</div>
</div>
<%= simple_format h(m.content) %> </div>
<div class="commentsCount">
<%= content_tag :span, pluralize(m.comments.count, 'comment'), :class => "view_all_comments" if m.comments.any? %>
</div>
<% if m.comments.any? %>
<% comments(m.id).each do |comment| %>
<div class="comment_container">
<%= link_to image_tag(default_photo_for_commenter(comment), :class => "commenter_photo"), commenter(comment.user_id).username %>
<div class="commenter_content"> <div class="userNameFontStyle"><%= link_to commenter(comment.user_id).username.capitalize, commenter(comment.user_id).username %> - <%= simple_format h(comment.content) %> </div>
</div><div class="comment_post_time"> <%= time_ago_in_words(comment.created_at) %> ago. </div>
</div>
<% end %>
<% end %>
<% if logged_in? %>
<%= form_for #comment, :remote => true do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :micropost_id, :value => m.id %>
<%= f.text_area :content, :placeholder => 'Post a comment...', :class => "comment_box", :rows => 0, :columns => 0 %>
<div class="commentButtons">
<%= f.submit 'Post it', :class => "commentButton", :disable_with => "Post it" %>
<div class="cancelButton"> Cancel </div>
</div>
<% end %>
<% end %>
</div>
</div>
From here this is where it gets confusing for me. I got slightly further using link_to but then decided I'd prefer not to have the url to the comments count show in the browser status bar. This is why I switched to using span.. but now it's not quite easy to do what I wish to do as I can't use the link_to/remote => true now.
How do I make it so when a user clicks the comment count span an ajax call is made pointing to:
def load_comments
#load_comments = Comment.where(:micropost_id => params[:id])
respond_to do |format|
format.js { render :load_comments }
end
end
I thought about putting a click function in users.js but how would I pass the params of the micropost that is in the each loop in the code above into users.js? I don't think it's possible.
All my comment posting is done via ajax but because I used forms for these it was so much easier for me to just add remote => true and create some js templates and do something on success of ajax post.
Not sure if I'm even going about this the right way. I'd appreciate some help/advice from more experienced rails programmers.
Kind regards
Rails partial
#Display all the comments based on local passed to this partial
# Initially pass limit as 2(or whatever you want). then on click of span pass limit as nil. then you can check if limit is nil you can query the model without limit specifier.
<% #comments = Comment.custom_find(#your_params) %>
<% #comments.each do |comment| %>
<%= comment.title %>
<% end %>
javascript/jquery
function load_all_comments(id)
{
new Ajax.Updater('show_comments',
'<%=url_for(:controller => "your_controller", :action => "your_action")%>', {
parameters: {'id':id },
method: 'get',
onSuccess: function(request){
div_comments = document.getElementById("partial_comments_list");
div_comments.innerHTML = request.responseText;
}
});
} // you can call this js function on span click. use jquery if you want.
Controller:
Then inside your_action of your_controller, dont forget to render the partial
render :partial => "show_comments", :layout => false
Edit:
you can even pass locals to your partial
render :partial => "show_comments", :locals => {:post => #post}
Using this every time your partial view will get updated, on the basis of locals you pass.
of course this is just an example not a complete code/solution.
There may be better ways. but this worked fine for me.
Another option is to just output all of the comments and hide the ones you don't want to show first. <div class="hidden_comments" style="display:none;"> a comment </div>
Then just have some javascript to show them when the span is clicked?
<script type="text/javascript">
$("#span_id").click(function() {
$('.hidden_comments').show();
});
</script>
This works great if you do not don't have a ton of comments.
If you really want to do it your way, I have done it before but it gets messy.
Put this in your application.js
$('.comment_span').live('click', function () {
$.get(this.data_url, null, update_row, 'json');
return false;
});
Your span would look like this:
<span class="comment_span" data_url="http://website.com/resource/more_comments">
show all comments
</span>
This example returns the data as json, so I used the update_row function to update replace the comments data.
function update_row(data, status) {
$("#comments-table").append(data.html);
};
Here is what my controller looked like:
def more_comments
#comments = Comments.all
if #comments
respond_to do |format|
format.js {
render :json => {
:html => render_to_string(:partial => "comments"),
}.to_json
}
end
end
end
You should do this via your index action.
Pass a param to it to determine if you want to show all comments or just the current set (I'd use will_paginate to handle this.
Haven't looked too deep into your code as I'm on my phone right now, but something like this:
class CommentsController < ApplicationController
def index
If params[:show_all] == "true"
#comments = Comment.all
else
#comments = Comment.where(foo: bar).paginate(per_page: 2, page: params[:page])
end
end
Then you have it respond to JavaScript and send the page param with your Ajax request