In my blog application, some posts appear as excerpts -- i.e., the user sees the first (say) 500 characters, and can click a link to view the entire post. Here is the relevant partial:
<% href = url_for post_path(:id => post) %>
<h1 class="title"><%= post.title %></h1>
<h2 class="published_on"><%= post.author %> wrote this <%= time_ago_in_words(post.published_on)%> ago</h2>
<div class="body">
<% if defined?(length) %>
<%= truncate_html(post.body, :length => length, :omission => "…<h1><a class='more' href=\"#{href}\">Click here for more!</a></h1>") %>
<% else %>
<%= post.body %>
<% end %>
</div>
However, instead of "Click here for more!" taking the user to a separate page, I'd like it to populate the rest of the post inline. Currently, I've been implementing this by putting the above snippet in the following div:
<div class="post" id="post_<%= post.id %>">
<%= render :partial => 'post_content', :locals => { :post => post, :length => 500 } %>
</div>
I then use the id of this div in my application.js to do the AJAX:
$(document).ready(function() {
$("a.more").click(function() {
var url = $(this).attr('href');
var id = url.split("/")[2]
$.get(url, null, function(data) {
$("#post_" + id).html(data);
});
return false;
});
});
This is obviously disgusting -- I don't want my javascript to depend on the location of the post's id in the link's href, but I don't know any other way for the javascript to know which post it is getting and therefore into which div the content should be inserted.
What's the best way to accomplish this? Should I just go back to using rails' AJAX helpers?
Horace, you are correct. You don't need the Rails helpers at all. To achieve unobtrusive JavaScript Nirvana, you will do well to avoid them (sorry Marc!)
Unobstrusive JS means avoiding embedded JS where possible. Indeed, try to keep things loosely coupled, but not entirely decoupled. In the example you gave us, it is easy, because we have a URL from the HREF to work with. Your JS does not need to "know" anything about how to request.
Here is how to send through the link from the HREF's blindly, and get Rails to respond with an ajax response (i.e. no layout).
SOLUTION:
<!------- Your HTML ------>
<h1 class="title">Schwein Flu Strikes Again</h1>
<h2 class="published_on">B.Obama wrote this 2 seconds ago</h2>
<div class="body">
SUMMARY SUMMARY
<h1><a class='more' href="/post/1234">Click here for more!</a></h1>
</div>
/********* Your JavaScript ***********/
$(document).ready(function() {
$("a.more").click(function() {
$containing_div = $(e).parents('div.body');
var url = $(this).attr('href');
$.ajax({
beforeSend : function(request) { request.setRequestHeader("Accept", "text/javascript"); },
/* Included so Rails responds via "format.js" */
success : function(response) { $(containing_div).empty.append(response); },
type : 'get',
url : url
});
return false;
});
});
########### Your Controller ###########
def show
@article = Post.find(param[:id])
respond_to do |format|
format.html { render :action => "show" and return }
format.js { render :partial => "post_content", :layout => false and return }
end
end
This also assumes you have RESTful routes or similar to handle /post/:id
And we're done! I believe there is something in that for all of us. :D
In this case, I don't think you should have the AJAX call at all. You already have the original post's entire content in post.body (you didn't fetch just the 500 characters from the db) Instead of using JQuery to make an ajax call, use it to hide the content past 500 characters and then when the user clicks the 'more' link it shows the rest of the div. You can do this entirely on the client side and save yourself the ajax.
In your partial:
<div class="body">
<%= post.body %>
</div>
Your JQuery:
var fullText = $('div.body').html();
$('div.body').html(fullText.slice(0,499) + '<a class="more" href=\"#\">Click here for more!</a>');
$('a.more').live('click', function(event) {
this.parent().html(fullText);
return false;
}
In my opinion for this kind of problems you should use rails' helpers.
HTML would go like :
My post bla bla bla <span id="more_of<%=post.id%>">more</span>
The more link would be :
<%= link_to_remote( "more",:update => "more_of_#{your_post.id}",:url =>{:controller => "myposts", :action=>"show_more", :id => your_post.id}, %>
And the controller would do something like :
post = Post.find_by_id(params[:id])
render :text => post.content.slice(100..post.content.size)
The other way that could be just as good is to load everything and then hide it :
bla bla bla this is my post blah <div style="display:none" onclick="this.style.display='block';">and this is the rest of the article</div>
(the onclick on a div tag might break in IE but this is to give you the idea)
To me that would be the best way to go unless you have a good reason of avoiding rails' helpers.
Hope that helps