In this tutorial we will create multi level comments. You must have seen comments on youtube and that they have levels depending on who replied to who. That’s exactly what we will build.
Intro Top
I’ve seen that most of scripts online execute query to check if comment has child elements. Let’s say you have 10 comments and each of them has 1 child element. You would execute 1 query to get all those 10 comment (first level) and then for each of them to check if it has child. And then for each child to check if that comment has childs to. That would be 1 + 10 + 10 = 21. That’s 21 query for nothing. It’s easier to do it with just one query that will load all comments and then to structure them in an multidimensional array. For that purpose we will use walker class that you can download here and read tutorial how we made it here. So let’s start working on it.
Folder Structure Top
Folder structure is very simple. jQuery folder has jQuery v1.3.2. We have two image files used for loader and to style comments. In our lib folder we have walker class that I mentioned before and we have some php file that we’ll later talk about.
MySQL Table Top
We’ll have very simple MySQL table. Table will have 7 fields.
CREATE TABLE IF NOT EXISTS `comments_tutor` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(128) COLLATE utf8_bin NOT NULL, `email` varchar(255) COLLATE utf8_bin NOT NULL, `url` varchar(255) COLLATE utf8_bin NOT NULL, `parent` int(11) NOT NULL DEFAULT '0', `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `message` text COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;
Html And Css Top
Now we’ll do some html and css. I won’t explan what I’ve done here. There are many good css and html tutorial on net.
main.css Top
This is content of main.css file.
@CHARSET "UTF-8";
body {
background-color: #f0f0f0;
}
#wrapper {
margin: 100px auto;
width: 600px;
}
#message {
margin-bottom: 15px;
}
#waiting {
color: #767676;
text-align: center;
}
.success {
background: #a5e283;
border: #337f09 1px solid;
padding: 5px;
}
.error {
background: #ea7e7e;
border: #a71010 1px solid;
padding: 5px;
}
/* Form */
#form fieldset {
border: none;
}
#form legend {
font-weight: bold;
}
#form label {
display: inline-block;
width: 70px;
vertical-align: top;
padding-top: 1px;
}
#form .text, #form textarea {
width: 300px;
}
#form textarea {
height: 100px;
}
#form .required {
color: red;
font-weight: bold;
}
/* Comments */
#comments ul {
margin: 0px;
padding: 0px;
}
#comments li {
list-style: none;
}
#comments .commentWrap p {
margin: 0px;
padding: 0px;
}
#comments .userTime {
color: #777575;
}
#comments li ul {
margin: 0px;
padding: 0px;
}
#comments li ul {
border-left: 1px solid #ced0d0;
}
#comments li li {
background: url('images/connect.gif') no-repeat transparent 0px 25px;
padding-left: 25px;
}
#comments .commentWrap {
border: solid 1px #ccc;
padding: 10px;
margin: 7px 7px 7px 0px;
background-color: #f2f2f2;
}
#comments li li .commentWrap {
background-color: #e6e4e4;
}
#comments p.message {
margin-top: 13px;
margin-bottom: 15px;
}
index.php Top
This is source of index.php file.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Multilevel Comments</title>
<link href="css/main.css" type="text/css" media="screen, projection" rel="stylesheet" />
</head>
<body>
<div id="wrapper">
<div id="message" style="display: none;">
</div>
<div id="waiting" style="display: none;">
Please wait<br />
<img src="images/ajax-loader.gif" title="Loader" alt="Loader" />
</div>
<div id="form">
<fieldset>
<legend>Write your comment</legend>
<p>
<label for="name"><span class="required">*</span> Name:</label>
<input type="text" name="name" class="text" id="name" />
</p>
<p>
<label for="email"><span class="required">*</span> E-mail:</label>
<input type="text" name="email" class="text" id="email" />
</p>
<p>
<label for="url">Url:</label>
<input type="text" name="url" class="text" id="url" />
</p>
<p>
<label for="msg"><span class="required">*</span> Message:</label>
<textarea rows="1" cols="1" id="msg" name="msg"></textarea>
</p>
<p>
<input type="hidden" name="parent" id="parent" value="0" />
<input type="button" name="submit" id="submit" value="Post" />
</p>
</fieldset>
</div>
<div id="comments">
</div>
</div>
<script type="text/javascript" src="js/jquery/jquery-1.3.2.js"></script>
<script type="text/javascript" src="js/ajaxSubmit.js"></script>
</body>
</html>
AJAX And Javascript Top
Now we will build JavaScript and add Ajax support. As you all know, when using jQuery you have to write your code when DOM is loaded like this. We’ll also add variable ajax that will disable making new AJAX call while there is still one active.
$(document).ready(function(){
ajax = false;
[...]
});
When we have this, we’ll write our code for AJAX. After our variable we’ll call function loadComments() that will load comment from our database using AJAX (we’ll create this function next).
So, this will be our function that will load comment from server. For more details about AJAX and what we did you can find here. This is code for function (paste it outside of upper part of code).
function loadComments()
{
$('#waiting').show(500);
$('#message').hide(0);
ajax = true;
$.ajax({
'type' : 'POST',
'url' : 'comments.php',
'dataType' : 'html',
'data' : {},
'success' : function(data){
$('#waiting').hide(500);
$('#comments').html(data);
},
'error' : function(XMLHttpRequest, textStatus, errorThrown) {
$('#waiting').hide(500);
$('#message').removeClass().addClass('error')
.text('There was an error.').show(500);
}
});
ajax = false;
}
Now we’ll create event listeners for two actions:
Event Listeners For `Reply` Link Top
This listeners calls function that check if comment exists. If comment does not exist, user can not make a reply to this comment. If comment exists, AJAX returns name of comment author that we use to construct string `Write reply to comment posted by %name%` where %name% is name returned from AJAX.
First we check if we are in AJAX, and if we are, we just return false. If we are not, then we do AJAX call. This is event listener.
$('.replayLink').live('click', function() {
if (ajax)
return false;
$('#waiting').show(500);
$('#demoForm').hide(0);
$('#message').hide(0);
ajax = true;
$.ajax({
'type' : 'POST',
'url' : $(this).attr('href'),
'dataType' : 'json',
'data' : {
'type' : 'reply'
},
'success' : function(data){
$('#waiting').hide(500);
if (data.error === true)
$('#message').removeClass().addClass('error')
.text(data.msg).show(500);
else {
$('#form legend').text('Write reply to comment posted by ' + data.name + '.');
$('#parent').val(data.id);
}
},
'error' : function(XMLHttpRequest, textStatus, errorThrown) {
$('#waiting').hide(500);
$('#message').removeClass().addClass('error')
.text('There was an error.').show(500);
$('#demoForm').show(500);
}
});
ajax = false;
return false;
});
Event Listener For Comment Submitting Top
This listener submits form to PHP file on server if there is no AJAX running.
$('#submit').click(function() {
if (ajax)
return false;
$('#waiting').show(500);
$('#demoForm').hide(0);
$('#message').hide(0);
ajax = true;
$.ajax({
'type' : 'POST',
'url' : 'post.php',
'dataType' : 'json',
'data' : {
'type' : 'post',
'name' : $('#name').val(),
'email' : $('#email').val(),
'url' : $('#url').val(),
'msg' : $('#msg').val(),
'parent' : $('#parent').val()
},
'success' : function(data){
$('#waiting').hide(500);
$('#message').removeClass().addClass((data.error === true) ? 'error' : 'success')
.text(data.msg).show(500);
if (data.error === false) {
ajax = false;
loadComments();
}
},
'error' : function(XMLHttpRequest, textStatus, errorThrown) {
$('#waiting').hide(500);
$('#message').removeClass().addClass('error')
.text('There was an error.').show(500);
$('#demoForm').show(500);
}
});
ajax = false;
return false;
});
PHP
Now we’ll do PHP part. This part generates comments and saves them. First we need to create db.php file. It’s just simple connection to database.
$con = mysql_connect('localhost', 'root', '');
mysql_select_db('tutoriali', $con);
Now we’ll do other parts as well.
Generating Comments Top
For this purpose we’ll use my walker class that you can get here.
First we have to include db.php and walker.php in our file.
require_once './db.php'; require_once './lib/walker.php';
Now we will create commentWalker class that will extend walker class. We will use this class to style our comments.
class commentWalker extends walker {
[...]
}
Now we’ll create function buildComments() that will be used to build comments. First we check if passed comments are null and if they are we use comments from parent class. If count of comments is less then one, then we echo message There are no comments to display. to buffer and return it. If there are comments, then we start building them. We just go through each comment and if it has child elements, we just call this function again. We also increment $lv by 1 to make comments flat at certain level. At end we just return what we’ve build.
function buildComments($comments = null, $lv = 0)
{
if (is_null($comments))
$comments = $this->_traced;
if (count($comments) < 1) {
ob_start();
?>
There are no comments to display.
< ?php
return ob_get_clean();
}
$lv += 1;
$commentHtml = '';
if ($lv <= 3)
$commentHtml .= '<ul>';
foreach ($comments as $comment) {
ob_start();
?>
<div class="commentWrap">
<p class="userTime">User < ?php if (!empty($comment->self->url)) : ?>
<a href="<?php echo $comment->self->url; ?>">< ?php echo $comment->self->name; ?></a>
< ?php else : echo $comment->self->name; endif; ?> wrote on < ?php echo date('F, j Y', $comment->self->date); ?>
in < ?php echo date('g:i a', $comment->self->date); ?>
</p>
<p class="message">
< ?php echo $comment->self->message; ?>
</p>
<p>
<a href="http://tutoriali/multiLevelComments/post.php?id=<?php echo $comment->self->id; ?>" class="replayLink">Reply</a>
</p>
</div>
< ?php
$html = ob_get_clean();
if (!empty($comment->childs)) {
$commentHtml .= '<li>';
$commentHtml .= $html;
$commentHtml .= $this->buildComments($comment->childs, $lv);
$commentHtml .= '</li>';
}
else {
$commentHtml .= '<li>';
$commentHtml .= $html;
$commentHtml .= '</li>';
}
}
if ($lv < = 3)
$commentHtml .= '</ul>';
return $commentHtml;
}
At the end we just have to create SQL query that will pull data from database and call this class and its functions.
$sql = 'SELECT `id`, `name`, `url`, `message`, `parent`, UNIX_TIMESTAMP(`date`) ' . 'AS `date` ' . 'FROM `comments_tutor` ' . 'ORDER BY `date`'; $comments = new commentWalker($con); echo $comments->loadResults($sql)->trace()->buildComments();
Saving Comments And AJAX Top
Here we have to require db.php. This will be fairly simple file. We’ll just check what kind of request are we doing and check data if they need to be checked. There is really noting complicated. We’ll use a variable $response to save response data and at the end echo it in JSON format.
require_once './db.php';
$response = array(
'error' => true,
'msg' => ''
);
while (true) {
if (!isset($_POST['type'])) {
$response['msg'] = 'You did not provide type.';
break;
}
switch ($_POST['type']) {
case 'reply' :
if (!isset($_GET['id'])) {
$response['msg'] = 'You did not provide comment ID.';
break 2;
}
$sql = 'SELECT `name`, `id` '
. 'FROM `comments_tutor` '
. 'WHERE `id` = %d '
. 'LIMIT 1';
if (($result = mysql_query(sprintf($sql, $_GET['id']), $con)) === false) {
$response['msg'] = 'Could not retrieve comment author.';
break 2;
}
if (mysql_num_rows($result) < 1) {
$response['msg'] = sprintf('There is not comment with ID `%s`.', $_GET['id']);
break 2;
}
$name = mysql_fetch_object($result);
$response['name'] = $name->name;
$response['id'] = $name->id;
break;
case 'post' :
if (!isset($_POST['name'])) {
$response['msg'] = 'You did not provide name.';
break 2;
}
if (!isset($_POST['email'])) {
$response['msg'] = 'You did not provide e-mail.';
break 2;
}
if (!isset($_POST['url'])) {
$response['msg'] = 'You did not provide url.';
break 2;
}
if (!isset($_POST['msg'])) {
$response['msg'] = 'You did not provide message.';
break 2;
}
if (!isset($_POST['parent'])) {
$response['msg'] = 'You did not provide comment ID.';
break 2;
}
if (!preg_match('/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/', $_POST['email'])) {
$response['msg'] = 'E-mail not valid.';
break 2;
}
if (!empty($_POST['url']) && !preg_match('#^((http|ftp|https)\:\/\/)(www\.)?([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((/?\w+/)+|/?)(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)?$#i', $_POST['url'])) {
$response['msg'] = 'Url not valid.';
break 2;
}
if ($_POST['parent'] > 0) {
$sql = 'SELECT `id` '
. 'FROM `comments_tutor` '
. 'WHERE `id` = %d '
. 'LIMIT 1';
if (($result = mysql_query(sprintf($sql, $_POST['parent']), $con)) === false) {
$response['msg'] = 'Could not check if comment exists.';
break 2;
}
if (mysql_num_rows($result) < 1) {
$response['msg'] = sprintf('There is not comment with ID `%s`.', $_POST['parent']);
break 2;
}
}
$sql = 'INSERT INTO `comments_tutor` ( '
. '`name`, `email`, `url`, `parent`, `message`'
. ') VALUES ( '
. "'%s', '%s', '%s', %d, '%s'"
. ')';
if (mysql_query(sprintf(
$sql,
mysql_real_escape_string($_POST['name']),
mysql_real_escape_string($_POST['email']),
mysql_real_escape_string($_POST['url']),
$_POST['parent'],
mysql_real_escape_string($_POST['msg'])
)) === false) {
$response['msg'] = 'Could not insert comment.';
break 2;
}
break;
default :
$response['msg'] = 'Invalid type.';
break 2;
}
$response['error'] = false;
$response['msg'] = 'Comment saved.';
break;
}
echo json_encode($response);
Conclusion Top
This will conclude our tutorial. You can download source file here or check demo here. Thank you for reading.
uhm, yea and sorry. i got it done. changed the url. anyway, it is all kicking good. KUDOs to you!
Hi there, this is a fabulous tut. Even though a little profound for a newbie like me. Will you be able to point me to a direction?
The comments work well but I am getting an error and am not able to utilize the ‘reply’ function. I get a “There was an error.”
I hope to get this working as i love the design much with the jQuery. There aren’t many of these tutorials around. Please and thank you.
I left my mail. May you prompt me as in your time of convenience?
Very good
I’m glad you did it. %s and %d are used for sprintf() and represent value taype (%s is for string and %d is for digit). Check sprintf() function for more information.
i got it:
1: i changed the simple quote by double
$sql = ‘INSERT INTO comments_tutor ( name, email, url, parent, message) VALUES ( “%s”, “%s”, “%s”, “%d”, “%s”)’;
2: i changed the path to the comments
<a href="comments/post.php?id=self->id; ?>” class=”replayLink”>Reply
Could u please explain me what “%s”,”%d” stand 4?
When i try to get: http://localhost/comments/post.php
i got a parse error on: $sql = ‘INSERT INTO comments_tutor ( name, email, url, parent, message) VALUES ( ‘”‘%s’, ‘%s’, ‘%s’, %d, ‘%s’”‘)’;
There are two reasons (maybe more but can’t think of any other now).
There are few solutions to these problems.
Yeah i did it
Did you follow my tutorial? You have setup database and changed setting to connect to database?
I tested it in localhost i got this: “There was an error”
Hmm, when? I don’t get it.
Thanx i was looking for dat for a century but i got this “There was an error”.
You get what? It works fine for me. What’s the problem?
I have aproblem with the comments… I get whenever a comment is posted… HELP fix this
No, level are unlimited. Function buildComments takes care for creating comments. If you remove
if ($lv < = 3) $commentHtml .= '<ul>';from function you'll see how that displays comments
very good. but most 3 levels?