Load Rails partial with passed values using AJAX? - ruby-on-rails

In my Rails app users have folders, and inside these folders are posts that they can add. Right now I have the folders displaying in my view like this:
<% current_user.folders.each do |folder| %>
<a href='#' id="addSubmissions">
<div id="post-container">
<%= folder.title %> <p id="created-time">Created <%= folder.created_at.strftime("%e/%-m") %></p>
</div>
</a>
<% end %>
What I'd like to do is load the submissions associated with a folder into a div when the #addSubmissions link is clicked. I've tried testing it out with this:
$('#addSubmissions').click(function(){
$('#post-container').append('<%= render :partial => 'contents', :locals => {:folder => folder } %>');
});
But it seems like that render code actually needs to be local beforehand.
The _contents.html.erb partial works, and is pretty simple:
<%= folder.title %>
<% folder.submissions.each do |i| %>
<%= i.title %>
<% end %>
How could I go about doing this? Again, I want to be able to click the link of a folder and load the submissions inside the folder into a div in the view. I'm thinking AJAX might be the solution, but I'm pretty new to Ruby so I don't know how to implement this.

This way it won't work. You need contoller method, which responds to JS, because you are fetching pure ruby variables.
Instead of:
<a href='#' id="addSubmissions">
you might try:
<%= link_to folder_content_path(folder), :remote=>true, :class=> "addSubmissions", :id=>folder.title %>
<div class="post-container" id="<%= folder.title %>">
<%= folder.title %> <p id="created-time">Created <%= folder.created_at.strftime("%e/%-m") %></p>
</div>
or other method name you want. Also note, that ID should be unique for page, otherwise you won't get expected result. Here I use folder name as ID, it probably unique, and it is easy way to find div we want modify with JS.
def folder_content
#folder=Folder.find(params[:id])
respond_to do |f|
f.js
end
end
folder_content.js.erb:
$("#"+"<%=#folder.title %>").append('<%= j render :partial => 'contents', :locals => {:folder => #folder } %>');

Related

Reloading page partially in Rails

I'm developing a music website in Rails. The landing page is divided and has a search form where you can look up bands stored in the DB. Inside the other division I embedded a player reproducing some random videos. I want the search results being displayed on the same landing page.
When I submit the form the page reloads and the player reloads also and starts playing a different video. What I actually want to achieve is that the player doesn't get interrupted while the search form is submitted.
Don't know if and how this is possible the way I want it. If I got it right this link describes one way to do that but I didn't get it to work.
home controller
def index
#bands = Band.search(params[:search]).order("name ASC")
#random_band = Band.order("RANDOM()").limit(1)
end
def preview
#bands = Band.search(params[:search]).order("name ASC")
render :partial => 'preview', :content_type => 'text/html'
end
_preview.html.erb
<table class="table"
<tr>
<th>Title</th>
<th>Year</th>
</tr>
<% #bands.each do |f| %>
<tr>
<td>
<%= f.name %>
</td>
</tr>
<% end %>
</table>
home#index
<!DOCTYPE html>
<html lang="en">
<form class="navbar-form navbar-left" role="search">
<%= form_tag({:action => 'preview'}, id: "search-form", :remote => true, :'data-update-target' => 'update-container') do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Bands" %>
<%= submit_tag "Search" %>
<% end %>
</form>
<div class="row">
<div class="col-md-6">
<div id="update-container">
<%= #random_band.first.name %>
</div>
</div>
<div class="col-md-6">
<%= video_tag("#{#random_band.first.name}.mp4", size: "460x390",
controls: true, autobuffer: true, autoplay: true) %>
</div>
</div>
</html>
application.js
$(function() {
/* Convenience for forms or links that return HTML from a remote ajax call.
The returned markup will be inserted into the element id specified.
*/
$('form[data-update-target]').live('ajax:success', function(evt, data) {
var target = $(this).data('update-target');
$('#' + target).html(data);
});
});
First problem: The form doesn't seem to find the preview action.
Second problem: If I change the content of the index with the preview action it does find the preview action obviously but it doesn't render the search results inside the update-container. Instead it renders them as a whole new page.
By the way, I know my way around Rails a bit but I've got absolutely no experience with Ajax.
You got most of your code very twisted and not really lean as far as I can tell. The short and very lean way to get your search results updated without reloading the rest of the page (including your player) could look like this. The index function should look like this:
//bands_controller.rb
def index
#bands = Band.search(params[:search])
end
You have to create the search-method inside of your Band-Model:
//Band.rb
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
You need to render your _preview.html.erb inside a div with a certain ID somewhere inside your view:
<div id="bands"><%= render 'preview' %></div>
Your form_tag has a lot of arguments that you won't need anymore:
<%= form_tag bands_path, :method => 'get', :id => "search_form" do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
<% end %>
The jquery I use for my forms looks like this.. Pretty simple in my opinion:
$(function() {
$("#search_form input").keyup(function() {
$.get($("#search_form").attr("action"), $("#search_form").serialize(), null, "script");
return false;
});
});
This fires the index.js.erb (you will have to create that) everytime you put a letter into your search field:
//index.js.erb (same folder as index.html.erb)
$("#bands").html("<%= escape_javascript(render("preview")) %>");
This should refresh your div. It's maybe a little work but that's the easiest way to implement an ajax search function into your index page that I could think of.
Sources:
Railscast#27
Railscast#240

Using multiple yields to insert content

I am trying to insert content on my page with yield but every time action removes whole content from the page. I have one main yield which is working fine:
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
But inside that new content which is displayed on one page I have another yield:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
When user clicks on the menu which is rendered, new content should be displayed below that menu.
admins/_menu.html.erb
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><%= link_to "Users", :controller => "admins", :action => "test" %></li>
<li><%= link_to "1", ... %></li>
<li><%= link_to "2", ... %></li>
<li><%= link_to "3", ... %></li>
</ul>
</div>
</div>
</div>
Controller:
class AdminsController < ApplicationController
def index
end
def test
#users = User.paginate(page: params[:page])
end
end
test.html.erb
<% content_for :admin do %>
<h1>All users</h1>
...
<% end %>
When I click on the option 'Users' from menu, page refreshes, menu disappears and nothing is displayed inside `body'. I want the content to be displayed below menu. How to use that second yield and accomplish this functionality?
I hope the question is not confusing. If question is confusing, please write me in comments and I will edit it immediately.
Thank you :)
So, when you go to the index page you will get the piece of html that will be placed in the main layout, and this piece of html look like this:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
This code will yield :admin properly.
When you go to the test page you do not have this html code anymore (since it only belongs to the index method). So, anything you put in the content_for(:admin) block will be ignored since no-one is printing it.
What you probably want to do is creating a shared layout for all your admin pages. Follow this guide and you'll have your solution.
Solution
Edit the application.html.erb layout using this:
<%= content_for?(:content) ? yield(:content) : yield %>
instead of
<%= yield %>
Then create an admins.html.erb file inside the layouts folder to handle your admin pages' layout. Something like this:
<% content_for :content do %>
  <div class="container">
<%= render 'admins/menu' %>
<%= yield %>
</div>
<% end %>
<%= render template: "layouts/application" %>
Will do fine. Then in the index.html.erb and test.html.erb just place regular HTML content, without using the content_for(:admin) block. Everything should work fine and you'll have your custom admin template, with a slightly different look from regular pages.
Calling yield doesn't work in helper modules, while content_for does, so you should replace your yield calls in the helper files.
Also noteworthy: using provide is recommended over content_for when you're only using the method in 1 place instead of multiple places. You'll get better performance since it won't leave the buffer open while looking for more content, and your intent will be clearer to other developers that may see your code. (see http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide)
I found that you have to add an yield (without actually outputing) before the namespaced tags.
<div>
<% yield %>
<div class="mt-3">
<div class="text-2xl tracking-wide font-bold text-gray-900">
heading
<%= yield :heading %>
</div>
</div>
<div class="relative bg-white rounded-xl shadow-xl mb-8 min-h-28">
<%= yield %>
</div>
...

Rails: 'Load More' button with Ajax & Kaminari

I would like to create a "load more" ajax pagination, with Kaminari.
I'm using this code :
class BienvenueController < ApplicationController
def index
#articles = Admin::Article.page(1).per(2)
respond_to do |format|
format.html
format.js
end
end
end
# Bienvenue#index
<div class="container" style="min-width:<%= #width %>px">
<%= render "shared/articles" %>
<%= link_to_next_page #articles, 'Load More', :remote => true, :id=>"load_more_link" %>
# Shared/articles
<% #articles.each do |a| %>
<article class="<%= a.rubrique.color %>">
<div class="sharing">
<%= image_tag "facebook-32.png" %>
</div>
<p class="color<%= a.rubrique.color %>"><i>Le <%= I18n.localize(a.created_at, :format => :long) %> par David Perrotin</i></p>
<h1><%= a.titre %></h1>
<div class="excerpt">
<%= a.chapo.html_safe %>
</div>
<div class="image">
<%= image_tag a.mainphoto.url(:medium), :width=>"100%" %>
</div>
<div class="contenu">
<%= a.contenu.html_safe %>
</div>
<div class="readmore">
<%= link_to "Continuer la lecture", article_path(a) %>
</div>
</article>
<% end %>
# index.js.erb
$('.container').append("<%= escape_javascript(render 'shared/articles')%>");
$('#load_more_link').replaceWith("<%= escape_javascript(link_to_next_page(#articles, 'Load More', :remote => true, :id=>'load_more_link'))%>");
But the problem is that when I click on "Load More", it always shows the two same articles, the partial is never refreshed with two more articles, like I would like.
I just ran into an issue with this that might help others. Depending on your version of jQuery, don't use replaceWith on the #load_more_link in index.js.erb.
There is a regression (http://bugs.jquery.com/ticket/13401) that an empty replaceWith does nothing, so on the very last page of your set, the link_to_next will be empty, making the line: $('#load_more_link').replaceWith(''); and thus will not replace the last "more" button, so you'll continually load the last page of your data set.
Fixed by updating jQuery version or use empty().html('...') instead of replaceWith.

How do I implement a show all comments feature in ruby on rails?

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

Rails: Link to partials?

At the moment I try to do following:
I created several partials (i.e. _show_signature.html.erb) for my user.
Now I want to show them on clicking a link.
In my user controller, I created a new action:
def show_signature
#is_on_show_signature = true
end
def show_information
#is_on_show_information = true
end
on my user show.html.erb i coded this:
<% if #is_on_show_information %>
<%= render :partial => 'show_information' %>
<% elsif #is_on_show_signature %>
<%= render :partial => 'show_signature' %>
<% end %>
and in my "navigationbar" i wrote:
<ul>
<li class="profile-tab">
<%= link_to 'Information', show_information_path %>
</li>
<li class="profile-tab">
<%= link_to 'Signature', show_signature_path %>
</li>
</ul>
In my routes.rb I wrote:
map.show_information '/user-information', :controller => 'user', :action => 'show_information'
map.show_signature '/user-signature', :controller => 'user', :action => 'show_signature'
now my problem:
clicking on my "information" link will redirect me to http://localhost:3000/user-information (cause I told him this path in routes.rb - I think) and I get an error:
uninitialized constant UserController
But that's not what I want... My user show path is something like:
http://localhost:3000/users/2-loginname
(by coding
def to_param
"#{id}-#{login.downcase.gsub(/[^[:alnum:]]/,'-')}".gsub(/-{2,}/,'-')
end
in my user model)
I want to link to somethink like http://localhost:3000/users/2-test/user-information.
Any ideas how it will work? Any ideas why I get this error?
As far as Rails conventions go, the model itself is singular (User) but the table (users) and controller (UsersController) are both pluralized. This can cause a significant amount of confusion at first, and even after years of working with Rails I still make the mistake of trying things like 'user = Users.first' which is, of course, not valid, as often you get to thinking about table names instead of class names.
Also, for toggling the display of elements on a page, you probably want to use the link_to_remote method which uses AJAX for updates instead of a page refresh. If you're okay with a full page refresh, those actions will need to redirect_to something, such as the page referrer, or you will get a blank page or error since the page template does not exist.
Typically what you do is:
<ul>
<li class="profile-tab">
<%= link_to_remote 'Information', show_information_path %>
</li>
<li class="profile-tab">
<%= link_to_remote 'Signature', show_signature_path %>
</li>
</ul>
Then each action is as you have specified, however, the page template show_information.rjs would look like:
page.replace_html('extra_information', :partial => 'show_information')
Keep in mind you will need to have a placeholder to receive the partial contents, so simply wrap your optional sections in an element with a specific ID:
<div id="extra_information">
<% if #is_on_show_information %>
<%= render :partial => 'show_information' %>
<% elsif #is_on_show_signature %>
<%= render :partial => 'show_signature' %>
<% end %>
</div>

Resources