Rails - display results of index and show actions via ajax - ruby-on-rails

I have a very simple Post resource with two actions, index and show. My template contains a sidebar with links to each previous post. I want the sidebar links to display their content (i.e. the results of the "show" action) via ajax
I know there are lots of excellent tuts that show you how to create a form that submits with ajax but this time I want to use it to display the contents of my index and show actions without page referesh
. Are there any decent tutorials out there that give tips on how to do this?
I reckon I need to create a show.js.erb file, and tell my index action to respond to js but I'm a little bit stuck getting any further. I don't quite know what to put in controller's show action or show.js.erb - it's a little difficult to visualise what I need to do
PS - using rails 3.0.7, jquery-1.5

Got this working, it was very simple in the end.
Step 1 - add :remote => true to links in sidebar
#application.html.haml
%nav#sidebar
- for post in #posts
= link_to post.title, post_path, :remote => true
%div#main
= yield
Step 2 - tell your controller to respond to JS on the show action
def show
#post = Post.find(params[:id])
#posts=Post.all # needed for sidebar, probably better to use a cell for this
respond_to do |format|
format.html # show.html.erb
format.js # show.js.erb
end
end
Step 3 - Create _post.html.haml
# _post.html.haml
%article.post
= sanitize post.body
Step 4 - Create show.js.erb and replace the html in the #main div with the contents of the _post partial (that we created in step 3)
# show.js.erb
$("#main").html("<%= escape_javascript(render #post) %>");
Now all the content is passed via ajax and it's working fine.

I don't have a full answer at hand, because I'm relatively new to this, too.
But, I would start by taking a look at JQuery's get() method. You should be able to use that to call the index or show method. Those methods should return the html that you want to display in the non-sidebar section of the page. You can use get()'s callback handler to place that HTML into the appropriate div on the page.
Sorry that this is a little vague, but if you play with this I bet you'll figure it out.

Related

Ajax calls - send_data and render a partial - two render methods in a single action

Users can download reports via a link, and next to the link is a text flag indicating whether or not they have downloaded the file already - an 'unread' alert. I hava a partial which shows a single item in an index of reports:
<p id="chapter_report_index_item">
<%= link_to "#{report.chapter_report_original_filename}", chapter_report_path(report), remote: true %>
<%unless check_read_status?(report) %>Unread
<%end%>
<p>
In chapter_reports_controller.rb I have -
def show
set_chapter_report
set_read_status_true(#chapter_report)
send_data #chapter_report.chapter_report.read, filename: #chapter_report.chapter_report_original_filename
respond_to do |format|
format.js
end
end
And in show.js, a call to re-render this single item partial -
$("#chapter_report_index_item").html( "<%= j (render(partial: 'index_item', locals: {report: #chapter_report})) %>" );
You'll note the check_read_status method in the view - essentially the idea is that when the partial is displayed, the unread flag is displayed if the user has not clicked on the download link.
My problem is, I'm trying to render two things in the action: the download, and the partial. What's the technique for avoiding this?
Essentially, I want a download button that changes when the file has been downloaded.
Update
I'm trying to solve this though a javascript to a helper (as per comments below). My problem is that send_file and it's ilk are not available as methods in application_helper.rb
Essentially I've got a jQuery call to a partial which just contains a call to the download method -
<%= report.class.name %>
<%= report.chapter_report_original_filename %>
<%= asset_download(report) %>
In the helper -
def asset_download(object)
object.chapter_report_original_filename
#send_file(object.chapter_report.read, filename:object.chapter_report_original_filename)
end
I've put the calls to .class.name etc to see what's getting through. When I uncomment the send_file call, I get the error -
ActionView::Template::Error (undefined method `send_file' for #<#<Class:0x0...
I'm guessing cos the method is available to the ActionController not ActionView.
So the question is - how do I make controller methods available in this context? Or do I find another way to do this?
Update 2 as per the answer below, you can call the send_file method by rendering a partial which contains the call through javascript. You need to make the method available in the helper as per this question.
You could split your action in two seperate actions (the download and the ajax call) and write some frontend (javascript) code that listens for the download click and triggers a second ajax call to the update_read_status action (for example via jquery).

Rails: How to render pages using AJAX and maintain DRY

I have a calendar page that shows a person's routine for an entire month. At the top, I have button for the previous and next months. When those are clicked, I make an AJAX call to the change_date action which updates the dates and finds the new routines. When the change_date.js.erb is called, do I have to write the code to change the content for everyday's actions again? This seems to be violating DRY.
Is there any way I can reuse the partials I used to populate the page on initial load to repopulate the page with the new values?
Of course, there is. Simply use render as you did in your html templates. Usually a setup that supports ajax and regular calls looks something like this (of course names are wildly guessed):
# controller
def change_date
#dates = Date.where(:date => params[:date])
respond_to do |format|
format.html
format.js
end
end
# change_date.html.erb
<ul id="calendar">
<%= render #dates %>
</ul>
# change_date.js.erb
$("#calendar").html("<%= escape_javascript(render #dates) %>");
# _date.html.erb
<li><%= date.date %></li>
So you see, you can simply use the same partial on both scenarios.
And just another suggestion: Depending on your setup it may be more restful to not have another method in the controller, but to use the show method which operates depending on a query param in this case...
I would load the person's routine via ajax when the page is first loaded as well.
I would encapsulate it in a js function and call that function when the page is first loaded and everytime the month is changed.

Rails - ability to link directly to views that are rendered via ajax

Looking for the best way to implement this. Currently I have a "show" page for Users - that shows all of a users' pictures.
def show
#user = User.find(params[:id])
#pictrues = #user.pictures
end
On that page, I have various tabs. When a user clicks on one of those tabs, an ajax call renders a view... particularly, it updates a partial that was previously showing all of a users pictures (and an additional partial for statistics). For the "show comments" example, it updates the partial with all of the pictures a user has commented on:
def show_comments
#user = User.find(params[:id])
#pictures = #user.picture_comments.map{ |p| p.picture }.uniq
respond_to do |format|
format.html
format.js
end
end
The show_comments.js.erb file looks like:
$("#user_content_container").html("<%= escape_javascript render(partial: 'shared/pictures', pictures: #pictures) %>");
$("div.user_header").html("<h4>Comments</h4><br/>");
$("#stat_container").html("<%= escape_javascript render(partial: 'shared/comment_stats', pictures: #pictures) %>");
What I want to do, is to keep the current functionality of the page. But also be able to link directly to the views that are rendered via ajax. For example, have a link on another page that goes directly to the users "show" page, as it is when the "comments" tab is clicked on.
I have a few ideas, but an not sure what the "cleanest" way of doing this would be. Let me know if you need any additional clarification, b/c I'm honestly having as difficult time wording this question, as I am in finding the best way to implement this!
This sounds like something that you might be able to solve using turbolinks. If you can update your app to use Rails 4, you get this bundled in. Otherwise, you can use the gem. For more information on how to do this, watch the railscast on turbolinks.
If you don't want to or can't use them, you could also try passing params in the url, and check for them when the page is loaded. You could use the params to modify the page in the same way that it would be modified after the AJAX call.

Rails: What does it actually mean to "render a template"

I've become a bit confused about the idea of "rendering" a "template" due to the way an author speaks about it in a book I'm reading.
My original understanding of "rendering a template" was that it meant that Rails is providing the content that is viewed on the screen/presented to the viewer (in the way that a partial is rendered) but the book I'm reading seems to be using the concept of "rendering a template" to also mean something else. Let me explain in context
This book (rails 3 in action) sets up a page layout using the conventional layouts/application.html.erb file, and then it "yields" to different view pages, such as views/tickets/show.html.erb which adds more content to the screen. that's all straightforward..
Within this view views/tickets/show.html.erb, there is a rendering of a partial (which is also a straightforward concept).
<div id='tags'><%= render #ticket.tags %></div>
Now within this partial there is, using ajax, a call to a "remove" method in the "tags_controller.rb" which is designed to allow authorized users to remove a "tag" from a "ticket" in our mock project management application.
<% if can?(:tag, #ticket.project) || current_user.admin? %>
<%= link_to "x", remove_ticket_tag_path(#ticket, tag),
:remote => true,
:method => :delete,
:html => { :id => "delete-#{tag.name.parameterize}" } %>
<% end %>
Now here is the "remove" action in the tags controller (which disassociates the tag from the ticket in the database)...
def remove
#ticket = Ticket.find(params[:ticket_id])
if can?(:tag, #ticket.project) || current_user.admin?
#tag = Tag.find(params[:id])
#ticket.tags -= [#tag]
#ticket.save
end
end
end
At the end of this remove action, the author originally included render :nothing => true , but then he revised the action because, as he says, "you’re going to get it to render a template." Here's where I get confused
The template that he gets this action to render is "remove.js.erb", which only has one line of jquery inside it, whose purpose is to remove the "tag" from the page (i.e. the tag that the user sees on the screen) now that it has been disassociated from the ticket in the database.
$('#tag-<%= #tag.name.parameterize %>').remove();
When I read "rendering a template" I expect the application to be inserting content into the page, but the template rendered by the "remove" action in the controller only calls a jquery function that removes one element from the page.
If a "template" is "rendered", I'm expecting another template to be removed (in order to make room for the new template), or I'm expecting content to be "rendered" in the way that a partial is rendered. Can you clarify what is actually happening when a "template" is "rendered" in the situation with the jquery in this question? Is it actually putting a new page in front of the user (I expected some sort of physical page to be rendered)
You're nearly there! Rendering a template is indeed always about producing content, but for a slightly wider description of content. It could be a chunk of html, for example an ajax call to get new items might produce some html describing the new items, but it doesn't have to be.
A template might produce javascript as it does in your second example. Personally I am trying to avoid this and instead pass JSON back to the client and let the client side js perform the required work.
Another type of rendering you might perform is to produce some JSON. APIs will often do this, but you might also do this on a normal page. For example rather than rendering some javascript to delete tag x you might render the json
{ to_delete: "tag-123"}
and then have your jQuery success callback use that payload to know which element to remove from the DOM, by having this in your application.js file
$('a.delete_tag').live('ajax:success', function(data){
var selector = '#' + data.to_delete;
$(selector).remove()
}
(Assuming that your delete links had the class 'delete_tag')
Rendering JSON like this isn't really a template at all, since you'd usually do this via
render :json => {:to_delete => "tag-#{#tag.name.parameterize}"}
although I suppose you could use an erb template for this (I can't imagine why though).
My understanding is that js.erb is "rendered" by executing the javascript functions within it. Very often something like the below is done:
jQuery(document).ready(function() {
jQuery('#element').html('<%= escape_javascript(render pages/content) %>');
});
There's a really succinct overview of rendering at http://guides.rubyonrails.org/layouts_and_rendering.html that may help as it also goes into the details of the ActionController::Base#render method and what happens behind the scenes when you use render :nothing (for example). Render but can be used for files or inline code as well -- not just 'templates' in the traditional sense.

Don't render layout when calling from Ajax

I have a rails action called index that renders the content for my page along with the layout. When I go to the /index action with a browser it works like expected. I want to be able to also render this action by calling it with Ajax, I am doing this using the following:
<%= link_to "Back", orders_path, :id => 'back_btn', :remote => true %>
<%= javascript_tag do %>
jQuery("#back_btn").bind("ajax:complete", function(et, e){
jQuery("#mybox").html(e.responseText);
});
<% end %>
When the action is called this way I would like it to render and pass the index action back, excluding the layout. How can I do this?
You should be able to add a format.js action to your controller action like so:
respond_to do |format|
format.js
format.html
format.json { render json: #foos }
Ideally, you would want to create a index.js.erb file that would build the contents of the page:
$('#foos_list').update("<%= escape_javascript(render(#foos)) %>");
If you're going to update the contents of a div, to basically update a whole page inside of a layout, then you're going to want to change it up a little bit. Inside of the format.js, you can do this:
format.js { render 'foos/index', :layout => false }
But if you're trying to go with an ajaxified front-end, may I recommend a framework for doing this, like Spine? It will go a long way in helping you build your site.
Also, using a framework like this will force you to separate your application per #Zepplock's second suggestion.
You can just detect if the request is an XML HTTP Request, then render a blank layout like so:
render layout: 'blank' if request.xhr?
You'll need to create a blank layout in app/views/layouts/blank.html.erb like this:
<%= yield %>
You need a way to let server know that there's a difference in request type. It can be done in several different ways:
Append a key value to the URL (for example layout=off) and change your controller logic to render data with no view. This is kind of a hack.
Make your controller return data via XML or JSON (controller will know what content type is being requested) then format it accordingly and present in browser. This is more preferred way since you have a clear separation between content types and is better suited for MVC architecture.
Create an API that will serve data. This will lead to separate auth logic, more code on client side, additional APi controller(s) on server etc. Most likely an overkill for your case

Resources