views:

902

answers:

2

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 => "&hellip;<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?

A: 

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

marcgg
why the downvote ?
marcgg
+4  A: 

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

crunchyt
Thanks -- grabbing the target for insertion by iterating through the anchor's parents is the trick I was looking for. (In practice you'd probably want to do the "setRequestHeader" stuff as part of an ajaxSetup call so you only have to do it once).
Horace Loeb
Hi Horace, I agree with your comment about the ajaxSetup call, but in my app it wasn't behave nicely so I took it out. I'll have to look into it again. Good luck with your app too, and viva la unobtrusive JS!
crunchyt