Hierarchical threaded comments; How I did it.

Filed in General, Technology

(Announced in "New blog feature: Hierarchical threaded comments!")

WARNING: Lots of technical jargon!

My blog is a MovableType blog developed by Six Apart Ltd. They also have other blog systems. They're a very smart company. You can read about their products on their web site. Like many other systems, MovableType allows you to add functionality via plugins.

One such plugin designed to implement threaded comments is called MTSimplyThreaded which I installed and experimented with. It could link comments to other comments but it couldn't produce a hierarchical comment scheme so it wasn't what I wanted.

Then I was told about MTThreadedComments. I read the documentation and it seemed to be exactly what I wanted, but it was old and apparently not compatible with my version of MovableType. Not to be dissuaded too easily, I searched for tips about running MTThreadedComments with my version of MovableType on google and I came across a couple of sites; one of them in Korean; which explained what adjustments to MovableType's code needed to be done. Strangely enough, it was the Korean site that told me exactly what I needed to know. I couldn't read the Korean but their code screen shots were self explanatory.

I examined MTThreadedComments' documentation and everything I could find on the internet. I didn't like MTThreadedComments' suggested method of responding to comments. Clicking on any 'Respond' link redirected the reader to another page with a copy of the article text or the comment text that the reader was responding to along with the comment form. This is normally ok but I don't like unnecessary page loads and wanted to come up with a more ideal (in my opinion) way of doing it. I liked the way MTSimplyThreaded worked and after analysing what data MTThreadedComments needed to work, I decided to implement a simple Ajax comment system. The investigation of MTThreadedComments and the analysis of what I wanted to do and how to do it took two full days.

What started out as a simple JavaScript script turned into something much more complicated (it's always the same), and IE's many css bugs made me reconsider my plan to implement MTThreadedComments via Ajax more than once, but I eventually finished it and I'm pretty pleased with it. I just wish that IE supported opacity (without requiring ActiveX). I can fully understand now why so many web developers despise IE. Why is it that a humongous company with unlimited resources can't make a decent (standards based) browser??? All I can say is thank God for Dean Edwards and his IE7 JavaScript script.

Now for the details.

MTThreadedComments on MT 3.3: The details

MT 3.33
MTThreadedComments 0.1.1 (Date: 2003/02/19 17:18:16)

MT Code Edits

You'll need to apply the following edits to MT's code:

(Taking a cue from the Korean site, the new code is coloured red.)

File: mt/lib/MT/App/Comments.pm

Subroutine: _make_comment()


$comment->ip($app->remote_ip);
$comment->blog_id($entry->blog_id);
$comment->entry_id($entry->id);
$comment->author(remove_html($nick));
$comment->email(remove_html($email));
$comment->url(is_valid_url($url, 'stringent'));
$comment->text($text);

## MTThreadedComments patch start ##
$comment->subject($q->param('subject'));
$comment->parent_id($q->param('parent_id'));
## MTThreadedComments patch end ##

File: mt/lib/MT/App/Comments.pm

Subroutine: _send_comment_notification()


my %param = (
blog_name => $blog->name,
entry_id => $entry->id,
entry_title => $entry->title,
view_url => $comment_link,
edit_url => $base . $app->uri_params('mode' => 'view', args => { blog_id => $blog->id, '_type' => 'comment', id => $comment->id}),
ban_url => $base . $app->uri_params('mode' => 'save', args => {'_type' => 'banlist', blog_id => $blog->id, ip => $comment->ip}),
comment_ip => $comment->ip,
comment_name => $comment->author,
(is_valid_email($comment->email)?
(comment_email => $comment->email):()),
comment_url => $comment->url,

## MTThreadedComments patch start ##
comment_subject => $comment->subject,
## MTThreadedComments patch end ##

File: mt/lib/MT/App/Comments.pm

Subroutine: view()


my $ctx = MT::Template::Context->new;
$ctx->stash('entry', $entry);

## MTThreadedComments patch start ##
$ctx->stash('comment_parent_id', $q->param('parent_id'));
## MTThreadedComments patch end ##

File: mt/lib/MT/Comment.pm

Subroutine: the root (i.e., the implicit) routine, at the beginning of the file


column_defs => {
'id' => 'integer not null auto_increment',
'blog_id' => 'integer not null',
'entry_id' => 'integer not null',
'author' => 'string(100)',
'commenter_id' => 'integer',
'visible' => 'boolean',
'junk_status' => 'smallint',
'email' => 'string(75)',
'url' => 'string(255)',

## MTThreadedComments patch start ##
'subject' => 'text',
'parent_id' => 'integer',
## MTThreadedComments patch end ##

and…


indexes => {
ip => 1,
created_on => 1,
entry_id => 1,
blog_id => 1,
email => 1,
commenter_id => 1,

## MTThreadedComments patch start ##
parent_id => 1,
## MTThreadedComments patch end ##

mt_comments Table

Next, you'll need to add two columns to the mt_comments table. I used phpMyAdmin.


comment_subject (text)
comment_parent_id (int)

Implementing my Ajax comments script: The details

If you plan on using my Ajax comments script (which you can get directly from my server) or something based on it:

MT Code Edits

File: mt/lib/MT/App/Comments.pm

Subroutine: post()
at the end of the routine:



return do_preview($app, $app->{query}, '');

return $app->redirect($comment_link);

This returns the comment preview html to the Ajax script which then inserts it into the existing individual archive page.

Comment Preview Template

You will need to edit your Comment Preview Template so that it only returns the html required to preview the comment. That means no <head>, no <body>, nothing except the comment itself and any tags that you need to format it. My xhtml code looks like this:


<MTIfNonEmpty tag="CommentPreviewSubject">
<h2><$MTCommentPreviewSubject$></h2></MTIfNonEmpty>
<$MTCommentPreviewBody$>
<div class="posted">
<p>Posted by: <$MTCommentPreviewAuthorLink spam_protect="1"$> |
<$MTCommentPreviewDate format="%a, %B %e, %Y, %H:%M"$></p>
</div>

Incidentally, the MTThreadedComments plugin does not provide a MTIfCommentPreviewSubject container tag so I had to use MTIfNotEmpty provided by Brad Choate's ifempty plugin.

Comment Error Template

You will also need to edit your Comment Error Template so that it only returns the minimum html required to view it within the Comment Form Error div if an error should occur during final submission of the comment. My xhtml code (it's just 3 lines) looks like this:


<h3>Comment Submission Error</h3>
<p>Your comment submission failed for the following reasons:</p>
<$MTErrorMessage$>

Individual Entry Archive edits

You'll need to add MTThreadedComments tags and JavaScript code to your template. For example;

The code that produces the comments list:


<MTIfCommentsActive>
<div id="comments-list">
<h3 id="comments">Comments</h3>
<MTRootComments>
<div id="comment-<$MTCommentID$>-parent" class="comment-parent">
<$MTInclude module="comments nested"$>
</div><!-- comment_parent -->
</MTRootComments>
</div> <!-- comments list -->
</MTIfCommentsActive>

The code for the 'comments nested' module:


<div id="comment-<$MTCommentID$>" class="comments-body">
<MTIfCommentSubject><h2><$MTCommentSubject$></h2></MTIfCommentSubject>
<div id="comment-<$MTCommentID$>-body">
<$MTCommentBody$>
</div> <!-- comment-<$MTCommentID$>-body -->
<div class="posted">
<p>Posted by: <$MTCommentAuthorLink spam_protect="1"$> | <$MTCommentDate format="%a, %B %e, %Y, %H:%M"$>
<MTIfCommentsAccepted> | <a href="javascript:void(0);" onclick="respond(<$MTCommentID$>, '<$MTCommentSubject remove_html="1" encode_js="1"$>', '<$MTCommentAuthor remove_html="1" encode_js="1"$>')">Respond to this comment</a></MTIfCommentsAccepted></p>
</div>
</div> <!-- comment-<$MTCommentID$> comments-body -->
<MTCommentIfChildren>
<div id="comment-<$MTCommentID$>-children" class="comments-children">
<MTCommentChildren>
<div id="comment-<$MTCommentID$>-child" class="comments-child">
<$MTInclude module="comments nested"$>
</div> <!-- comment-<$MTCommentID$>-child -->
</MTCommentChildren>
</div> <!-- comment-<$MTCommentID$>-children -->
</MTCommentIfChildren>

The code in my template that produces the "Create a comment regarding this article." link at the end of the comment list:


<MTIfCommentsAccepted>
<p><a href="javascript:void(0);" onclick="respond (0, '<$MTEntryTitle remove_html="1" encode_js="1"$>', '河國榮')">
Create a comment regarding this article.</a></p>
<MTElse>
<p>Comments to this entry are no longer being accepted.</p>
</MTElse>
</MTIfCommentsAccepted>

For reference, the MTThreadedComments plugin provides the following tags:


<MTRootComments>, <MTCommentChildren>, <MTCommentIfChildren>
<MTIfCommentSubject>, <MTUnlessCommentSubject>, <$MTCommentSubject$>
<$MTCommentPreviewSubject$>, <$MTCommentResponseSubject$>
<MTCommentParent>, <$MTCommentParentID$>, <MTCommentIfParent>, <MTCommentUnlessParent>
<MTCommentPreviewParent>, <$MTCommentPreviewParentID$>, <MTCommentPreviewIfParent>, <MTCommentPreviewUnlessParent>

Optional: An improved link to the comment upon comment submission.

When you submit a comment, MT currently usually in the Comment Notification email returns a link to the Individual Entry Archive page without including the comment # anchor. You can rectify this if you wish.

File: mt/lib/MT/App/Comments.pm

Subroutine: post()
in the # Form a link to the comment section:


# $comment_link = $entry->permalink;
$comment_link = $entry->permalink . '#comment-' . $comment->id;

Note. My Individual Entry Archive template assigns ids to each of the comments
using the format id="comment-<$MTCommentID$>". Your format may and probably does differ. You will need to adjust the $comment_link code accordingly.

MT 3.34/3.35 and FastCGI

Note. This setup works with MT 3.34/3.35 and FastCGI as long as you don't use the FastCGI version of AdminScript; i.e., in the config file;


AdminScript mt.cgi
CommentScript mt-c.fcgi
TrackbackScript mt-t.fcgi
SearchScript mt-search.fcgi
ViewScript mt-view.fcgi

If you use the FastCGI version of AdminScript, building the Individual Entry Archive pages will fail; something to do with this line of code in the get_comments() subroutine of the MTThreadedComments.pl code:


my @comments = sort { $a->created_on <=> $b->created_on }

MT produces an error which begins with these three lines:


unknown column: parent_id for class MT::Comment at lib/MT/Object.pm line 283
MT::Object::AUTOLOAD('MT::Comment=HASH(0x92a1cc8)') called at /path_to_cgi-bin/mt/plugins/MTThreadedComments.pl line 123
MT::get_comments('MT::Template::Context=HASH(0x9168aa4)', 'MT::Entry=HASH(0x9175904)', 'undef') called at /path_to_cgi-bin/mt/plugins/MTThreadedComments.pl line 153

If anyone knows why this line doesn't work, please leave a comment.

Other details

You will of course need to write (or copy) the css for the form, etc. I use Dean Edwards' IE7 JavaScript script to help standardise the look of my blog on IE so don't count on my css to help you code for IE.

No support!   I can offer no support for this implementation of MTThreadedComments. If you don't understand the instructions, then it's probably too difficult for you and you shouldn't attempt it.

Above all else; and it should go without saying; back up your MT installation before you begin editing the code and templates.

That's all folks!

New blog feature: Hierarchical threaded comments!

Filed in General, Technology

After finishing "They’re Playing Our Song (2007)", I needed rest. My vocal chords needed rest. More than a week before the show began, my chords had begun to inflame. I had worked them too hard with strenuous vocal exercises in the mornings followed by rehearsals every day for 8 weeks. When my chords began to feel tight, I consulted my doctor and was informed that my chords were inflaming. I halted the morning vocal exercises but it was too late. For the entire week that we performed "They're Playing Our Song", my chords were straining through every single show. I was only able to perform successfully and get through the ordeal by not speaking between shows, taking anti-inflammatories at night before sleep and using a Japanese powder known as 龍角散 'Dragon Horn Powder' before each show. Still, my vocal chords suffered tremendously; especially during those shows when our microphones weren't working; and they needed rest. I needed to shut up.

The week after the show, I couldn't completely abstain from talking. My parents were here and I don't get many opportunities to be with them so chatting with them was a blessing not to be ignored. Only after they had left Hong Kong and returned to Australia could I really allow my vocal chords to relax; no singing and as little talking as possible.

No talking meant no going out to eat and chat with friends. It meant staying at home.

For the first three days, I spent countless tiring hours identifying and verifying the content on my backup DVDs and CDs. I have 15,000 photographs in my Aperture library and none of them are backed up. My DVD backups haven't been very reliable and backing up to normal DVDs is frankly unrealistic because of the number of discs involved (approx. 40 to back up my Aperture library) and the amount of time required (more than 24 uninterrupted hours), so I've had to consider other options for a better backup plan. I have decided to backup incrementally to an external 750G Seagate Barracuda drive which I have ordered. I am also experimenting with backing up to DVD+R DL (double layer) discs which would halve the number of disc swaps required for every backup operation. Eventually, I will investigate Blu-Ray backups. With up to 25G per disc, it would simplify backups immensely.

Then I began working on something else blog related which might or might not interest you although it will affect many of you.

I have never been satisfied with the comment system in my blog. It didn't allow people to respond to other comments and there was no comment hierarchy. I reply to quite a few comments but usually a long time after those comments have been submitted and my replies consequently get separated from the comments I'm replying to. I didn't like it so I decided to change it ;-)

For those of you not interested in the intricacies of how I did it, here is the short story. It took 7 full days of coding (perl, javascript, css and xhtml) but my blog now has full hierarchical threaded comments!!! You can now respond directly to another comment and your comment will be displayed immediately beneath that comment. My implementation is a simple version of Ajax and does not require a page reload. IE (Internet Explorer) was a terror to work with but I eventually worked out the problems so my comment system is working with IE too.

So that's it. You can now reply to each other's comments. No arguments please ;-)

Incidentally, my vocal chords are much better although not yet fully recovered. I won't be singing for at least another two weeks.

(For those of you interested in how I did it, you can find all the gory details here.

Will London have me?

Filed in General, Life

I have just one goal this week, one thing that must be completed before the holiday weekend begins. I must complete and dispatch my application to study a (Postgraduate Diploma or MA) degree in Musical Theatre at the University of London.

Henry (the director of "They’re Playing Our Song (2007)") gave me a list of institutions that he thought would have something to offer me in my quest to improve my acting skills. I checked all of their web sites and only two offered subject courses which I felt would be appropriate to my current needs. After further consideration, I decided to apply to the University of London.

There is no guarantee that I'll be accepted. In fact, it might be a long shot but I have to try anyway. I have no other degrees. All I have is 19 years of experience, acceptable acting and singing abilities, and a desire to learn. Combined with the fact that I would be a foreigner and a 'senior' student, this will hopefully be enough to get me into the course.

I won't know if I get accepted for at least a month. I'll let you know what happens ;-)

For those concerned, rest assured that I will continue to work with TVB. It is simply my hope that I'll be able to expand my horizons and branch out into movies and musical theatre, especially musical theatre. Acting and singing on stage is just so incredible!

Update (April 4, 2007)

The application was completed and handed over to the local FedEx people this afternoon at 5.30pm. Now I need to relax (my vocal chords are still inflamed; my doctor said that I really must shut up for at least a few days) and prepare just in case they consider my application and request an audition.

Update (April 24, 2007)

I have received an invitation to an audition to be held in London on June 23. It's going to be tough but I'll do my best. Six weeks to go…

Thank you all.

Filed in Events, They’re Playing Our Song (2007), Work

This article will serve as the conclusion to what has been the adventure of my life, "They're Playing Our Song". For ten weeks, we rehearsed and performed the play and it'll always be a highlight in my life.

"They're Playing Our Song" has changed me. It has changed my life. I'm more confident now. I've taken one of the lead roles in a two-actor musical and successfully performed a two-hour show with singing, dancing and acting. I set out to prove to others that I could do it and I ended up proving to myself that I could do it. I think that result was more important than anything else I might have gained.

I loved the experience. I loved the play. I loved the music. I loved the singing and acting. I will be more proactive from here on. I will be sorting out more opportunities to work again in musical theatre and in movies. I will also be studying again (if the institutions will have me) because I want to be the best I can be. I have a few ideas. The future will show whether those ideas come to fruition or not. If you want to know what they are; sorry, you'll have to wait and see ;-)

I believe in the chains of life, and I wonder how many new chains will develop from my involvement in this play. Only time will tell.

I need to thank a few people.

Thank you Henry for giving me the chance to be in this production. It was a very special gift. Thank you for being an incredible person and putting up with me for so long. I am very grateful that we overcame all obstacles, that we succeeded in producing the show, and that we became friends.

Thank you Sompor for being the person you are. You're extraordinary in many ways, especially in the context of Hong Kong. May you find that which your heart desires.

Thank you April for always being there, for being so approachable and for being so efficient with our tickets ;-)

Thank you Eli, Kin Kin and Charles for your guidance, your support and your friendship. You're all talented performers and I feel privileged and lucky to know you all. I'll be sure to keep in touch with each and every one of you.

Thank you Katrina and Karen for being so professional, for being so clear about the rules and your requirements, and for being so helpful and reliable.

Thank you Sai for the photographs. They were fantastic. And a special big thank you for the flowers that you sent to the theatre. I'll always remember them.

Thank you Thomas for your help with the advertisement designs and the jpegs. The 300dpi files were extremely valuable. My parents now have a signed framed A3 copy to remember this special occasion by.

Thank you Aian and Coco for the hair styling, and for your moral support during the show.

Thanks to all of my friends who came and watched the show to support me, even though it is not their practice to watch stage productions.

(anybody would think I've just won the Oscars…)

Thank you TVB for the wonderful interview on 「娛樂直播」. I have never felt so special.

A big thank you to all of the newspapers and magazines that interviewed us and published information about the play. Also a big thank you to the radio stations and the hosts that interviewed us.

Thank you 發哥 for one special phrase you uttered when we met that day in January on Clear Water Bay: 「我唸你唔懶嘅」. You'll never know how much that meant to me, and how it kept me going during the hard times.

In closing.

To answer a common question; we recorded the final performance but it will not be publicly available. There are two reasons for this. First, we didn't purchase resale rights for the show, and second, you can never fully appreciate a stage production unless you're actually there. The recording will never feel as good or as fun as the actual production.

In time, I will be scanning all of the interviews that we had and making them available here. I'm not a very diligent guy though (unless I'm working on a musical ;-) so give me some time to get it all together.

There will be no more articles about "They're Playing Our Song". As a final goodbye, I leave you with a link to this photo album. These photos were taken by Sai during our final full dress rehearsal. I hope you like them as much as I do.

Take care everyone, and never give up on your dreams before you've tried. As Burt Munro says in the movie "The World's Fastest Indian" starring Anthony Hopkins; "If you don't follow through on your dreams, you might as well be a vegetable".