logo

AJAX Multi-Level Comments

Add comment

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.

Muti Level Comments Folder Structure

Muti Level Comments Folder Structure

MySQL TableTop

We’ll have very simple MySQL table. Table will have 7 fields.

  • id – unique ID
  • name – name of comment author
  • email – e-mail of comment author
  • url – url of comment author (optional)
  • parent – ID of parent comment (if no parent then equals to 0)
  • date – date that comment was posted
  • message – comment message
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:

  1. when user clicks on `Reply` link
  2. when user submits comment.

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.

Related Posts
  • 16.07.2009 -- Advanced PHP User Login (16)
    If you ever had a bank account you are familiar with TAN-s (Transaction Authentication Number). What...
  • 29.06.2009 -- jQuery AJAX tutorial (108)
    This tutorial cover some of basics about jQuery and AJAX. We will build AJAX form submit where we wi...
  • 16.07.2010 -- Packt Special Offer (0)
    Packt Publishing has new offer for this hot summer. Have a someones birthday soon and don't know ...
  • 23.12.2009 -- Learning Resources (0)
    Reader Satish requested a list of tutorials where he could learn about PHP, MySQL and jQuery. Since ...
  • 27.07.2010 -- Upload Images With MySQL (6)
    [tinytoc level="1"]Intro[/tinytoc] Ok, let's continue with tutorial requests. This tutorial will ...
  • 21.05.2010 -- Multi-Language Site (28)
    It's been a while since I last posted something because I was very busy. So let's do something usefu...
  • 07.04.2010 -- Edit XML (29)
    I had an idea about creating tutorial that will cover manipulating XML in PHP. The idea was to creat...

logo

55 comments to “AJAX Multi-Level Comments”

  1. Your SQL query is not valid

  2. Tosin says:

    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

  3. ali says:

    thanks …..

  4. Aljie says:

    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.

  5. Hmm, I don’t really get what you want from me. Can you explain it a bit more?

  6. pepe says:

    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

  7. Mankandan says:

    Really well yaar…hey guyz..

  8. 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!

  9. 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.

  10. frii says:

    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.

  11. MAWarGod says:

    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.

  12. Hmm, you have source at the bottom :) .

  13. deller says:

    where do i get the source for THIS comment system

  14. mike says:

    that was great. but if you adding anti-spam function, i will more awesome.

  15. thaks script.

    janual says:

    Open file: comments.php

    Find Lin 48

    Code:

    http://tutoriali/multiLevelComments

    Change to

    Your Site Link.

Leave a Reply


 *


 *


logo
logo
Powered by Wordpress | Designed by Elegant Themes | CopyRight ©2012 php4every1.com