Wednesday, November 02, 2005

Javascript Implementation Of Recent Comments

In my search for ways to make the information on this site more timely and accessable, I came across the so-called Farrago Recent Comments Hack. It does some pseudo-magical stuff with Blogger tags, but I thought I could go one better by encapsulating the code in a proper class, providing comments, and enhancing the code for readability. Plus I made it work with my chosen date format and have made it easier for all of you to do the same.

Once you have read how the original works, follow these steps to use my enhanced version. I do assume you know something about what you are doing here. This is not for those unfamiliar with their template.

1. The following is the Javascript which does the processing. There are two ways of using it.

a) The preferred way is to save it as its own text file, with a name like "rc.js" and then upload it somewhere you have space on the web. Link to it from the header of your template with a line like: <script type="text/javascript" src="http://www.x.com/rc.js"></script>

b) If you don't have external storage, put this code in the header of your template, wrapped in script tags. To be explicit, this line goes first: <script type="text/javascript" language="JavaScript1.2">, then the code below, then finally this line: </script>.

OK, here's the code.

/*
Blogger Recent Comments
v1.05

Based on the Farrago Recent Comments Hack v1.03
boggerhacks.blogspot.com
(c) 2004 Ebenezer Orthodoxy

Statement: I would GPL the code if the original author would.
Mods by: Robin Parmar
Visit: noisetheatre.blogspot.com
*/

// our class
function RecentComments() {
// options to change
this.displayAmount = 10;
this.displayTemplate = '<li>[name]:<br/>[title]</li>';
this.displayPre = '<ul>';
this.displayPost = '</ul>';
this.displayLink = true;

// properties
this.comments = new Array();
this.title = '';
this.itemurl = '';

// methods
this.SetTemplate= rcSetTemplate;
this.SetAmount = rcSetAmount;
this.SetLink = rcSetLink;
this.SetPrePost = rcSetPrePost;

this.SetTitle = rcSetTitle;
this.SetUrl = rcSetUrl;

this.SortDate = rcSortDate;
this.AddComment= rcAddComment;
this.Display = rcDisplay;

// this line uses my date converter method
this.DateConvert = rcDateConvert;

// comment out the previous line and uncomment the
// next line to use original date format
// this.DateConvert = rcDateConvertDefault;

// or write your own and insert it
}

// simple property setters: these are used by process
function rcSetTitle(x) {
this.title = document.getElementById(x).innerHTML;
}
function rcSetUrl(x) {
this.itemurl = x;
}

// these are used by user to customise
function rcSetTemplate(x) {
this.displayTemplate = x;
}
function rcSetAmount(x) {
this.displayAmount = x;
}
function rcSetLink(x) {
if (x==0) {
this.displayLink = false;
} else {
this.displayLink = true;
}
}
function rcSetPrePost(x, y) {
this.displayPre = x;
this.displayPost = y;
}

// date format converter
// insert your own here depending on the format you use for comment dates
// this one converts from:
// 01 November, 2005 16:35
// to:
// 11/01/2005 16:35:00
function rcDateConvert(dt) {
var s = dt.split(' ');
var d = s[0];
var m = s[1];
var y = s[2];
var t = s[3];

var MonthHash = new Array();
MonthHash['January'] = '01';
MonthHash['February'] = '02';
MonthHash['March'] = '03';
MonthHash['April'] = '04';
MonthHash['May'] = '05';
MonthHash['June'] = '06';
MonthHash['July'] = '07';
MonthHash['August'] = '08';
MonthHash['September']= '09';
MonthHash['October'] = '10';
MonthHash['November'] = '11';
MonthHash['December'] = '12';

// trim off comma
m = m.substring(0, m.length-1);

return MonthHash[m] + '/' + d + '/' + y + ' ' + t + ':00';
}

// default converter: does nothing
// use if your comment date format is:
// mm/dd/yyyy hh:mm:ss
function rcDateConvertDefault(dt) {
return dt;
}

// given a date string this returns a sorted representation
function rcSortDate(strDate) {
strDate = this.DateConvert(strDate)

var d = new Date(strDate);

var day = '' + d.getDate();
if (day.length==1) {
day = '0' + day;
}
var month = '' + (d.getMonth()+1);
if (month.length==1) {
month = '0' + month;
}
var hour = '' + d.getHours();
if (hour.length==1) {
hour = '0' + hour;
}
var min = '' + d.getMinutes();
if (min.length==1) {
min = '0' + min;
}
var sec = '' + d.getSeconds();
if (sec.length==1) {
sec = '0' + sec;
}
var sortDate = '' + d.getFullYear() + month + day + hour + min + sec;
return sortDate;
}

// adds to global comments array
function rcAddComment(title, url, id, a, datestamp) {
var author = a;
var expt = '';
var st = '';

// grab content of our hidden layer containing all items
var html = document.getElementById('comm' + id).innerHTML;

// strip out whitespace
while (html.indexOf("\n") > -1) {
html = html.replace("\n", "");
}
while (html.indexOf(" />") > -1) {
html = html.replace(" />", "/>");
}
while (html.indexOf(" <a/>") > -1) {
html = html.replace(" <a/>", "<a/>");
}

var htmll = html.toLowerCase();
var pos1 = htmll.lastIndexOf('<br><a></a>posted by');
var pos2 = htmll.lastIndexOf('<br><a></a><a></a>');
var pos3 = htmll.lastIndexOf('<br/><a/><a/>');
var pos4 = htmll.lastIndexOf('<br/><a></a><a></a>');
var aoffset = pos1 + 6;

if (pos3 > -1) {
pos2 = pos3;
}
if (pos4 > -1) {
pos2 = pos4;
}
if (pos2 > -1) {
pos1 = pos2;
aoffset = htmll.lastIndexOf('<a><b> </b></a>');
if (aoffset == -1) {
aoffset = htmll.lastIndexOf('<a><b></b></a>') - 1;
}
}

if (pos1 > -1) {
author = html.substr(aoffset+15, html.length-1);
expt = html.substr(0, pos1-4);
} else {
expt = html;
}
expt = expt.replace(/(<([^>]+)>)/ig, "");

if (expt.length > 50) {
expt = expt.substr(0, 50);
if (expt.lastIndexOf(' ') > -1) {
expt = expt.substr(0, expt.lastIndexOf(' '));
}
expt += '...';
}
expt = expt.replace('"', "\"");
expt = expt.replace("'", "\'");

author = author.replace("<A ", "<a ");
if (!this.displayLink) {
author = author.replace(/(<([^>]+)>)/ig, "");
}

// build a template string of HTML
st = this.displayTemplate.replace('[name]', author);
st = st.replace('[title]', '<a title="' + expt + '" href="' + url + '#c' + id + '">' + title + '</a>');

// prefix with date for sorting purposes
st = this.SortDate(datestamp) + st;

// accumulate on our array
this.comments.push(st);
}

function rcDisplay() {
// most recent comments first
this.comments.sort();
this.comments.reverse();

if (this.displayPre.length >0) {
document.write(this.displayPre);
}

for (i=0; i<10 && i < this.comments.length && i < this.displayAmount; i++) {
var s = this.comments[i];

// strips off date prefix
s = s.substr(14, s.length-1);
document.write(s);
}

if (this.displayPost.length >0) {
document.write(this.displayPost);
}
}


2. Depending on the comment date format you use, you may need to provide your own conversion method. This class works with the original Farrago format and my format as well. If you know a bit of Javascript it's pretty easy to extend, following the code example here.

3. Edit your template to put the following in the sidebar where you'd like the comments to appear. Your formatting may be different, but it'll be pretty darned similar. This whole block is wrapped in tags to ensure it only appears on the main page, because it produces an empty list on item pages and a misleading list on archive pages.


<!-- START RecentComments 1.05 -->
<MainPage>
<h2>recent comments</h2>
<script type="text/javascript" language="JavaScript1.2">
var rc = new RecentComments();
rc.SetTemplate('[name]: [title]<br/>');
rc.SetPrePost('', '');
rc.SetLink(0);
rc.SetAmount(5);
</script>
<Blogger>
<span id="comm<$BlogItemNumber$>" style="visibility:hidden; position:absolute;">
<BlogItemTitle><$BlogItemTitle$></BlogItemTitle>
</span>
<script type="text/javascript" language="JavaScript1.2">
rc.SetTitle('comm<$BlogItemNumber$>');
rc.SetUrl('<$BlogItemPermalinkURL$>');
</script>
<BlogItemCommentsEnabled><BlogItemComments>
<span id="comm<$BlogCommentNumber$>" style="visibility:hidden; position:absolute;">
<$BlogCommentBody$>
</span>
<script type="text/javascript" language="JavaScript1.2">
rc.AddComment(rc.title, rc.itemurl, '<$BlogCommentNumber$>', '<$BlogCommentAuthor$>', '<$BlogCommentDateTime$>');
</script>
</BlogItemComments></BlogItemCommentsEnabled>
</Blogger>
<script type="text/javascript" language="JavaScript1.2">
rc.Display();
</script>
</MainPage>
<!-- END RecentComments 1.05 -->


4. This example shows how the various options can be set from directly in your template code, so that you do not need to directly edit the Javascript file. There are four methods called in the first script block above. But you may be just as happy with the defaults.

rc.SetTemplate() sets the template string for each entry in the comment list. The placeholder "[name]" will get filled by the author's name, and "[title]" with the title of the post. The default is <li>[name]:<br/>[title]</li> which sets up each comment as an HTML list item.

rc.SetPrePost() takes two string parameters containing the HTML you want to be written before and after the comment list. The default is <ul> and </ul>, again for a simple list implementation.

rc.SetLink() takes the number "0" to turn off author links. Otherwise, by default, the author's name will link to their page.

rc.SetAmount() takes a number up to 10 indicating how many comments to display, 10 being the default.

So, in the example above we have changed the default code from an HTML list to a simple sequence of lines seperated by line breaks. We have turned off name linking, and set the maximum number of comments to 5.

5.
Following the Javascript, the tag section performs some magic. Remember that everything within a Blogger tags gets repeated for each of your posts. It is a loop construct. The first invisible span prints out the item (that is to say, the article) title with its own special ID.

The Javascript following this sets the title referencing that ID, and sets the URL using the appropriate blog tag.

Then we use a similar trick to grab the comment info. Blog tags loop over each comment, set in its own invisible span. The following Javascript associates the title and URL we already set with further details from the comment.

The final section, outside the Blogger tags, simply calls a method to display the results.

Note: I do not recommend you change any of the code in this section.

6. Once you have saved your template with these changes, and republished the site, you should have a lovely list of the most recent comments made to your blog. Note that mousing over the title reveals the first characters of the comment.

All is not perfect however. I have discovered that some comments seem to be ignored by this list, presumably because they are for articles too old to be on your main page. But altering the tags to access these would result in every article in your entire blog being included in invisible spans on your home page. The resulting slowdown is likely not worth it.

Think of this as "recent comments on recent articles" and you have the right idea!

Finally
Post any ideas for enhancements here. My regards to the original author, who did a great job of hacking Blogger code. My contribution is only to make this more understandable.

Addendum: 2006.03.17
I have made the explanation in section 4 a bit more explicit and added in the complete text of sections 5 and 6.

RELATED POSTS

21 comments:

robin said...

I have updated this significantly, putting in a full set of methods for setting options, versioning the file so we can keep track of changes, adding in the missing MainPage tags, and allowing for customisation of the code pre and post the list.

Buckles said...

Trying your hack - Getting "NNaN"

I have tried different time stamps then saved, republished and tried again.

What am I missing?

Buckles said...

I got it. Feel kinda stupid.

I used the correct time stamp that the java was looking for and it works like a charm.

Thank You

www.bucklesw.com

Buckles said...

I do have a question.

This work great finding the comments on the page. I have mine set for 7 days. I am posting almost daily - with several posts a day.

Is there a way to have the java script search the archives?

Will doing that bog the browser down?


Thanks - and again it's working as advertised.

robin said...

This already seems to slow a page down so I'm sure that searching the archives would be a significantly bigger hit. As for how to do that... I don't know!

Anonymous said...

Thank you very much.

I have successfully implemented your hack into my blog.

Anonymous said...

Thanks! Coding well explained. I wonder if it is possible to enhance it further by displaying partial of comments and give the option for the readers to click a link to the post to read further? *aiks... asking too much? :P

TheShaz said...

I've been using this implemention of the Comment Hack on my 1st web site
Buckles Blog


Works Great!

Helped a friend get their blog going - using the same script on theirs doesn't show the link, just the name of the poster.

All my days are Circus days.

Any help would be great!

Christina said...

I got the names to display correctly/chronologically, but the text size is bigger than the rest of my sidebar. how do i change this?

robin said...

Christina: I do not know why that should be, but it would have nothing to do with this code, since formatting is not changed in any way. Try wrapping the display part of the code in a span that you format with CSS the way you want.

robin said...

TheShaz: I have looked over the code and I do not see what is wrong. Turning on the Javascript console reveals the only error messages are those from Blogger's own nav bar -- good work guys! At this point all you could do is go through the painstaking JS debugging process with popup windows. Sorry I can't spot the issue.

TheShaz said...

Thanks for the reply.

Anonymous said...

Hi there. Nice code you posted here. It seems to be working on my weblog. The only issue I have is the same as buckles mentioned before: the NNaN keeps appearing. I've made some little edits to my js-file, but that does nothing. Might this be because my weblog is not in English?

robin said...

sv: This is almost always because your date format is not supported. Read over that section of the instructions.

A.X said...

How should I change the code if I would like it to show "None" when I don't have any comment on the main page? I have my blog display only one post on the main page so it looks strange when no one post a comment yet.

Everything works fine despite of this problem.

Thanks!!!!!!!!

A.X said...
This comment has been removed by a blog administrator.
mAn[S]o0r said...

thanks! works like a charm!!

check it out at wordofmansoor.blogspot.com

Martin said...

Great work, thank you!

The Itinerant said...

wow, great! thanks i found this, i was able to put a recent comment section but still going to test how will it work, tnx! Merry Xmas!

Anonymous said...

Hi!

I would like to know this "Javascript" is it still work today?

It for classic template is it ok?

robin said...

Actually, I think Recent Comments are now broken. I am moving towards a custom blog system so I doubt I'll be putting in time to fix this. But if anyone has a solution, help us out by posting it here.

Post a Comment