If you have noticed the way Youtube, Digg, Reddit formats your comments, you must have seen that your comments can be replied and the replies can be replied further (until a certain level.)
In this tutorial we are going to achive the same.

BUILD THE TABLE

Here I have created a table called 'threaded_comments'.

id — Unique id of the comment.
author — Name of the author of the comment.
comment — Main comment body.
created_at — The time when comment is made.
parent_id — Id of the comment to which the current comment is a reply. The default value of this is 0 ('zero') (i.e the default value of a non-reply comment).
Add some dummy data to it.
INSERT  INTO `threaded_comments`(`id`,`author`,`comment`,`created_at`,`parent_id`) VALUES 
(1,'cyberbuff','Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Phasellus sed tellus.','2009-05-30 18:18:19',0),
(2,'abhisek','this is a reply to comment #1','2009-05-30 18:20:53',1),
(3,'arijeet','An individual comment...','2009-05-30 18:20:21',0)


THE LAYOUT

Our layout is going to look like this:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Threaded Comments</title>
</head>
<body>
<div id='wrapper'>
<ul>
 <li class='comment'>
 <div class='aut'><!-- NAME OF THE AUTHOR --></div>
 <div class='comment-body'><!-- COMMENT BODY --></div>
 <div class='timestamp'><!-- TIMESTAMP --></div>
 <a href='#comment_form'>Reply</a>
  <!-- The following is an example of child comment -->
  <ul>
   <li class='comment'>
   <div class='aut'><!-- NAME OF THE AUTHOR --></div>
   <div class='comment-body'><!-- COMMENT BODY --></div>
   <div class='timestamp'><!-- TIMESTAMP --></div>
   <a href='#comment_form'>Reply</a>
   
   </li>
  </ul>
 </li>
</ul>
<form id="comment_form" action="post_comment.php" method='post'>
 <label for="name">Name:</label>
 <input type="text" name="name"/>
 <label for="comment_body">Comment:</label>
 <textarea name="comment_body" id='comment_body'></textarea>
 <input type='hidden' name='parent_id' id='parent_id' value='0'/>
 <div id='submit_button'>
  <input type="submit" value="Add comment"/>
 </div>
</form>

</div>
</body>
</html>
Put the above code in index.php.

This is just a mock layout. Here we first add an <ul> tag and then for each comment we create a list element. If the comment is a reply to some other comment, then we create another <ul> element within the list element. We list the replies within it. We continue this process until all replies are listed.

After listing the comments, we add the comment form. Notice that we have a hidden input field with the name 'parent_id'. We are going to pass the id of the comment id in it, if it's a reply.


ADD SOME STYLE

Since this tutorial deals mainly with PHP, explaining CSS is out of its scope. But this is the CSS I have used.

html, body, div, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, blockquote,
pre, form, fieldset, table, th, td { margin: 0; padding: 0; }

body {
font-size: 14px;
line-height:1.3em;
}

a, a:visited {
outline:none;
color:#7d5f1e;
}

.clear {
clear:both;
}

#wrapper {
 width:480px;
 margin:0px auto;
 padding:15px 0px;
}

.comment {
 padding:5px;
 border:2px solid #7d5f1e;
 margin-top:15px;
 list-style:none;
}

.aut {
 font-weight:bold;
}

.timestamp {
 font-size:85%;
 float:right;
}

#comment_form {
 margin-top:15px;
}

#comment_form input {
 font-size:1.2em;
 margin:0 0 10px;
 padding:3px;
 display:block;
 width:100%;
}

#comment_body {
 display:block;
 width:100%;
 height:150px;
}

#submit_button {
 text-align:center; 
 clear:both;
}
Just add the above code within a style tag.


CONNECT TO THE DATABASE

Create a config.php file and put the following code in it.

<?php
$hostname = 'YOUR HOSTNAME'; //it's localhost in all all cases
$db_username = 'YOUR DATABASE USERNAME';
$db_password = 'YOUR DATABASE PASSWORD';
$dbname = 'YOUR DATABASE NAME';
$link = mysql_connect($hostname, $db_username, $db_password) or die("Cannot connect to the database");
mysql_select_db($dbname) or die("Cannot select the database");
?>


LIST THE COMMENTS

Now, we include the config.php in index.php with:

<?php
include("config.php");
?>
Now, we remove everything within the ul tag and put the following code there:
<?php
$q = "SELECT * FROM threaded_comments WHERE parent_id = 0";
$r = mysql_query($q);
while($row = mysql_fetch_assoc($r)):
 getComments($row);
endwhile;
?>
Here first we select all non-reply comments and then pass the comments in a function called getComments which we will define in the next step.


THE MAGIC FUNCTION!

We create a file functions.php and put the following code in it.

<?php
function getComments($row) {
 echo "<li class='comment'>";
 echo "<div class='aut'>".$row['author']."</div>";
 echo "<div class='comment-body'>".$row['comment']."</div>";
 echo "<div class='timestamp'>".$row['created_at']."</div>";
 echo "<a href='#comment_form' class='reply' id='".$row['id']."'>Reply</a>";
 /* The following sql checks whether there's any reply for the comment */
 $q = "SELECT * FROM threaded_comments WHERE parent_id = ".$row['id']."";
 $r = mysql_query($q);
 if(mysql_num_rows($r)>0) // there is at least reply
  {
  echo "<ul>";
  while($row = mysql_fetch_assoc($r)) {
   getComments($row);
  }
  echo "</ul>";
  }
 echo "</li>";
}
?>
This function expects an associative array (which is a comment fetched from the table). It creates a list element and parses it. Then, it checks whether there are any comment whose parent_id is the same as its. If there's any such comment, it again executes the same function. The process goes on until no replies are found. Notice, each "Reply" link has a classname "reply" and has an id which is the id of the comment itself. We will pass this id to the hidden input field of the comment form, if it's a reply.

Now that we have created the function, include the functions.php in index.php with:
<?php
include("functions.php");
>

ADD REPLIES

As I said earlier, we would pass the id of a comment to the hidden input field of the comment form, if the user wants to reply to it. For this, we add jquery to our index.php and some code. Just add the following code under the head tag:

<script type='text/javascript' src='jquery.pack.js'></script>
<script type='text/javascript'>
$(function(){
 $("a.reply").click(function() {
  var id = $(this).attr("id");
  $("#parent_id").attr("value", id);
 });
});
</script>
When you click on a "Reply" link, the above code passes the id of that link to the hidden input field of the comment form.

ADD COMMENTS

Now, we create post_comment.php. Just put the following code in it.

<?php
include("config.php");
$author = mysql_real_escape_string($_POST['name']);
$comment_body = mysql_real_escape_string($_POST['comment_body']);
$parent_id = mysql_real_escape_string($_POST['parent_id']);
$q = "INSERT INTO threaded_comments (author, comment, parent_id) VALUES ('$author', '$comment_body', $parent_id)";
$r = mysql_query($q);
if(mysql_affected_rows()==1) {
 header("location:index.php");
}
else {
echo "Comment cannot be posted. Please try again.";
}
?>
It's very simple. First, it sanitizes all the data and then inserts into the table. If the insertion is successful, it redirects to index.php, and if the insertion fails, it gives an error message.

That's it!


Conclusion

This is a very simple mockup what happens behind the scene. It doesn't cover data-validation and other aspects.

Labels: , ,

blog comments powered by Disqus
Blogger Àl said...
Well. I'm very surprised with your code. Why is there not a post_id in your table?

I think that making a lot of queries when you can make a single one asking for all the comments for a post and then sort them with PHP.
Blogger Abhisek said...
Hi Àl!
I got your point.
Then, You must be surprised to see I have not added a URL field in the table (for the URL of the author).

Actually, this tutorial is not meant to cover all aspects. This is just a mock-up of how things work.
Blogger william said...
Well done, Abhisek. I always like your posts... and feed subscription emails coming. I think waht I like most about your site is the way you lay it all out - not that it's the most uniquely, most efficiently designed setup in code/layout but that it's 100% functional - and no offense to any other sites, that is what's the most important thing to me as a developer :P.

(ps. i used part of your url shortener code to create my new url shortener now rank'd <400k on alexa called scrnch.me - obviously, i did a whole heck of a lot more but used your example on it to guide me thru some of it :) - thanks again for your time to layout code well.