Saturday, February 11, 2006

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

No comments:

Post a Comment