May 13, 2008

JavaScript Date Helpers

Listen to this articleListen to this article

It's all the rage these days to have timestamps displayed in words to indicate how long ago some event occurred. You know something like "less than a minute ago" or "about 2 months ago", etc. You'll see plenty of examples on news sites, blog entries, and bug tracking tickets to name but a few.

If you've ever had to build this kind of thing in Rails you'll be familiar with all the Date Helper methods that make the task pretty trivial. The problem is that the result is fixed to whatever the date was when the page was rendered and as a result these timestamps go stale very quickly.

Save refreshing the page every minute--or hour or whatever--just to update the times, I figured what was needed was a little but of client-side action. Rather than send the text in the HTML, I decided to instead send the raw timestamps and have the browser periodically generate the textual representation.

To this end, I blatantly copied two methods from the afore-mentioned Rails helper-- distance_of_time_in_words(from, to), and time_ago_in_words(from)--and, taking some liberties along the way, converted them to JavaScript:

Date.prototype.distance_of_time_in_words = function(to) {
  distance_in_milliseconds = this - to;
  distance_in_minutes = Math.abs(distance_in_milliseconds / 60000).floor();

  if (distance_in_minutes == 0) {
    words = "less than a minute";
  } else if (distance_in_minutes == 1) {
    words = "1 minute";
  } else if (distance_in_minutes <= 44) {
    words = distance_in_minutes + " minutes";
  } else if (distance_in_minutes <= 89) {
    words = "about 1 hour";
  } else if (distance_in_minutes <= 1439) {
    words = "about " + (distance_in_minutes / 60).round() + " hours";
  } else if (distance_in_minutes <= 2879) {
    words = "1 day";
  } else if (distance_in_minutes <= 43199) {
    words = (distance_in_minutes / 1449).round() + " days";
  } else if (distance_in_minutes <= 86399) {
    words = "about 1 month";
  } else if (distance_in_minutes <= 525599) {
    words = (distance_in_minutes / 43200).round() + " months";
  } else if (distance_in_minutes <= 1051199) {
    words = "about 1 year";
  } else {
    words = "over " + (distance_in_minutes / 525600).round() + " years";
  }
  
  return words;
};

Date.prototype.time_ago_in_words = function() {
  return this.distance_of_time_in_words(new Date());
};

Now all I do is periodically invoke a function that calls one or other of these methods and updates the text of whatever display element is appropriate. Even better, because the raw timestamps have timezone information in them, the display doesn't suffer from, in my case here in Australia, always being 10 hours out because the server is sitting in the US with a US date/time.

Giving the Anchor tag some Ajax Lov'n

Listen to this articleListen to this article

It seems I'm forever needing to submit links using an XMLHttpRequest rather than the default full-page refresh. One approach commonly used in the Rails community is to render each link with the JavaScript already in place. My preferred approach however is to keep the HTML as free from JavaScript as possible and unobtrusively add behaviour using LowPro.

LowPro already comes with a built-in behaviour for links but sometimes I need something little more complex than simply submitting the request and so I usually end up doing the following:

anchor = ...;
new Ajax.Request(anchor.href, { method: "get", parameters: ... });

Granted that's not a lot of effort but it still felt as though I were repeating myself and that the overall intention of my code was largely obscured by the infrastructure. It then struck me that submitting a form using the Prototype JavaScript framework is almost trivial:

form = ...;
form.request({ parameters: ... });

So I cooked up a version for anchors as well:

Element.addMethods("A", {
  request: function(anchor, options) {
    new Ajax.Request(anchor.href, Object.extend({ method : "get" }, options || {}));
  }
});

Now I can submit links in pretty much the same was as I do forms:

anchor = ...;
anchor.request({ parameters: ... });

I'm wondering what other possibilities might occur were I to add a serialize() method to extract the request parameters.

Shameless Plugs

Recommend me on Working With Rails

Simian (Similarity Analyser): Rapidly identifies duplication in Java, C#, C, C++, COBOL, Ruby, JSP, ASP, HTML, XML, SQL, Visual Basic source code and even plain text files.

Beginning Algorithms: A good understanding of algorithms, and the knowledge of when to apply them, is crucial to producing software that not only works correctly, but also performs efficiently.

Creative Commons License
This weblog is licensed under a Creative Commons License.

Powered by
Movable Type 3.2