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.
IntroTop
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 StructureTop
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 TableTop
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 CssTop
Now we’ll do some html and css. I won’t explain what I’ve done here. There are many good css and html tutorial on net.
main.cssTop
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.phpTop
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 JavascriptTop
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` LinkTop
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 SubmittingTop
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 CommentsTop
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 AJAXTop
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);
ConclusionTop
This will conclude our tutorial. You can download source file here or check demo here. Thank you for reading.
Your SQL query is not valid
Marijan. I tested the script in my local server and it dis played errors like this:
Fatal error: Uncaught exception ‘Exception’ with message ‘Could not load result.’ in C:\wamp\www\commentscript\multiLevelComments\multiLevelComments\lib\walker.php:83 Stack trace: #0 C:\wamp\www\commentscript\multiLevelComments\multiLevelComments\comments.php(79): walker->loadResults(‘SELECT `id`, `n…’) #1 {main} thrown in C:\wamp\www\commentscript\multiLevelComments\multiLevelComments\lib\walker.php on line 83
what do you think is wrong with walker.php
thanks …..
I’m still newbie but i understand all. thank you for the code.. I have request to you mr. Marijan Šuflaj, email me if you have new codes..
thnk you.
Hmm, I don’t really get what you want from me. Can you explain it a bit more?
Hi, I need your help!
Existing SQL table that I could join?
There is a board post, each post is the same time the text is not assigned.
thanks
Really well yaar…hey guyz..
Well, I guess you don’t create valid SQL query. Try echoing it before executing to see what’s wrong. I guess, field you’re selecting does not exist so you should add it to table. Also you may have code like this
"FROM someTable". "WHERE field = value"
which seems to be OK but when created you get
"FROM someTableWHERE field = value".As you can see, someTableWHERE is interpreted as table name.
One more thing that’s important. Escape $param!
You could use nested queries. First select 20 parent comments and then for each of those select nested comments in a sub query. That could work if I understand what you want.
hi, how can i add a “WHERE” in comments.php ?
like this: .’WHERE `some_field` like $param’
without error msg:
Fatal error: Uncaught exception ‘Exception’ with message ‘Could not load result.’ in Z:\home\localhost\www\mysite\coments\lib\walker.php:83 Stack trace: #0 Z:\home\localhost\www\mysite\coments\comments.php(79): walker->loadResults(‘SELECT `id`, `n…’) #1 {main} thrown in Z:\home\localhost\www\mysite\coments\lib\walker.php on line 83
ty.
Hello is there a way to limit the post results per page? like showing only say 20 parent post, and using collapsed replies after say 3? this would speed up page load times.
Hmm, you have source at the bottom
.
where do i get the source for THIS comment system
that was great. but if you adding anti-spam function, i will more awesome.
thaks script.
janual says:
Open file: comments.php
Find Lin 48
Code:
http://tutoriali/multiLevelComments
Change to
Your Site Link.