Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting jQuery timeago plugin to recognize newly loaded DOM elements

I'm using the jquery timeago plugin in my Rails 3 application. I have a comments section on my posts#show view that automatically refreshes every 30 seconds with the AJAX used in Railscasts episode #229 "Polling for Changes". The _comment partial that is loaded by jQuery contains the created_at time of the comment which uses the timeago Rails helper method to create attr tags with the correct time format.

When a comment is submitted and ajax loads the comment, the new comment DOM element isn't recognized by the jQuery timeago plugin. So instead of the time of the comment being displayed as "about a minute ago" it displays "2010-11-21 23:08:36 UTC".

I've googled this problem of course and found suggestions regarding the use of .live(), .delegate() or the livequery plugin.

comments/index.js.erb:

<% unless @comments.empty? %>
  $("#comments").append("<%=raw escape_javascript(render(@comments)) %>").timeago();
<% end %>

comments/show.js.erb:

$("#comments").append("<%=raw escape_javascript(render(@comments)) %>");

public/javascripts/application.js:

$(function() {
  if ($("#comments").length > 0) {
    setTimeout(updateComments, 30000);
  }
});

function updateComments () {
  var post_id = $("#post").attr("data-id");
  if ($("#comments").length > 0) {
    var after = $(".comment:last-child").attr("data-time");
  } else {
    var after = "0";
  }
  $.getScript("/posts/" + post_id + "/comments?after=" + after)
  setTimeout(updateComments, 30000);
}

helpers/application_help.rb:

module ApplicationHelper
  def timeago(time, options = {})
    options[:class] ||= "timeago"
    content_tag(:abbr, time.to_s, options.merge(:title => time.getutc.iso8601)) if time
  end
end

layouts/application.html.erb:

<!DOCTYPE html>
<html>
  <head>
    <%= stylesheet_link_tag 'style' %>
    <%= javascript_include_tag :defaults %>
    <%= javascript_include_tag 'jquery.timeago' %>
    <%= csrf_meta_tag %>
  </head>
  <body>
    <!-- ... -->
    <script type="text/javascript">
      $("abbr.timeago").timeago();
    </script>
  </body>
</html>

comments/_comment.html.erb:

<div class="comment" data-time="<%= comment.created_at.to_i %>">
  <%- if comment.commenter.empty? -%>
  <%- else -%>   
    <span class="name">
      <%- if comment.email.blank? -%>
        <%= comment.commenter %>:
      <%- else -%>
        <a href="mailto:<%= comment.email %>"><%= comment.commenter %>:</a>
      <%- end -%>
    </span>
  <%- end -%>

  <%= simple_format @comment.body %>

  <span class="time">
    <%= timeago(comment.created_at) %>
  </span>
</div>

posts/show.html.erb:

<div id="post" data-id="<%= @post.id %>">
  <%= link_to @post.title, @post %>
  <%= simple_format @post.content %>

  <% unless @post.comments.empty? %>
    <div id="comments">
      <%= render @post.comments %>
    </div>
  <% end %>
</div>

<div id="replyform">
  <%= render "comments/form" %>
</div>

The AJAX polling and timeago plugin functionality works fine everywhere else, it's just when I make a comment and that comment appears with AJAX from another browser that I run into this issue. Any suggestions, resources or code would make my week. Thank you for reading my post.

like image 791
66tree Avatar asked Nov 22 '10 00:11

66tree


2 Answers

Your code calls $("abbr.timeago").timeago(); in a script block in the application template. This runs as the page is loaded for this first time, and enables timeago functionality on matching elements that exist at that moment. Any matching nodes that are added dynamically later on -- for instance your AJAX partial -- do not get timeago functionality.

One option is to call $("abbr.timeago").timeago() again after adding dynamic content. However, that can be hard to keep track of and remember to do as you add new functionality, which is why I use the livequery plugin instead. Include the livequery script in your page and replace $("abbr.timeago").timeago(); with the following:

$("abbr.timeago").livequery(function () { $(this).timeago(); });

This enables timeago on any matching nodes now and matching nodes added dynamically.

like image 199
John Avatar answered Nov 19 '22 11:11

John


Once load() has done its thing, ajaxComplete() is called. I would advise creating a function called ready_or_not():

$(document).ready(function(){
  ready_or_not();
});

$(document).ajaxComplete(function(){
  ready_or_not();
});

function ready_or_not(){
  $(".timeago").timeago();
}

There may be certain code that you only want to run when the document is ready, or when Ajax is complete. But for both, put it inside the ready_or_not() function.

like image 4
rybo111 Avatar answered Nov 19 '22 13:11

rybo111