Nested Comments using Codeigniter, AJAX

Introduction

Here you will see how to create nested comments using Codeigniter AJAX MySQL. In my previous tutorial I have shown how to create Nested comment system in PHP, AJAX. Here I am going to show you how to do the same thing but using Codeigniter 3, MySQL and AJAX.

Prerequisites

Codeigniter 3.1.10, PHP 7.0.15 – 7.4.27, Apache 2.4 (Optional), MySQL 5.x – 8.x

Project Setup

It’s assumed that you have setup PHP and Codeigniter in Windows system.

Now I will create a project root directory called codeigniter-nested-comments anywhere in the physical drive.

Now move all the directories and files from CodeIgniter framework into codeigniter-nested-comments directory.

I may not mention the project root directory in subsequent sections and I will assume that I am talking with respect to the project root directory.

You need to create assets folder in parallel to application folder for putting all asset files such as js, css, images etc.

Configuring Auto-load

Now modify application/config/autoload.php file for auto-loading html, url, file, form and app helpers.

app is the custom helper file which will be created later.

I am also auto-loading database driver to load every time whenever required.

$autoload['helper'] = array('html', 'url', 'file', 'form', 'app');
$autoload['libraries'] = array('database');

Custom Helper – app.php

Create application/helpers/app_helper.php file with the following source code:

<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

if (!function_exists('mysql_to_php_date')) {

    function mysql_to_php_date($mysql_date) {
        $datetime = strtotime($mysql_date);
        $format = date("F j, Y, g:i a", $datetime);
        return $format;
    }

}

In the above helper file I have created one function to convert MySQL date to PHP date for better readability.

Configuring Database

Go to location application/config/database.php file and change database parameter values as shown below. Do not forget to update accordingly if you have different values.

'hostname' => 'localhost',
'username' => 'root',
'password' => 'root',
'database' => 'roytuts',

MySQL Table

Create a MySQL tables – blog and blog_comment in “roytuts” database.

Table – blog

For MySQL server version 8.x, use the following structure:

CREATE TABLE `blog` (
  `blog_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
  `blog_title` varchar(225) COLLATE utf8mb4_unicode_ci NOT NULL,
  `blog_slug` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `blog_text` text COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`blog_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


For MySQL server version 5.x, use the following structure:

USE `roytuts`;

/*Table structure for table `blog` */

DROP TABLE IF EXISTS `blog`;

CREATE TABLE `blog` (
  `blog_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `blog_title` varchar(225) NOT NULL,
  `blog_slug` varchar(255) NOT NULL,
  `blog_text` text NOT NULL,
  PRIMARY KEY (`blog_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

Table – blog_comment

For MySQL server version 8.x, use the following structure:

CREATE TABLE `blog_comment` (
  `comment_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
  `comment_text` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `comment_date` timestamp COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `parent_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL,
  `blog_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`comment_id`,`blog_id`),
  KEY `fk_blog_comment_blog1_idx` (`blog_id`),
  CONSTRAINT `fk_blog_comment_blog1` FOREIGN KEY (`blog_id`) REFERENCES `blog` (`blog_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

For MySQL server version 5.x, use the following structure:

USE `roytuts`;

/*Table structure for table `blog_comment` */

DROP TABLE IF EXISTS `blog_comment`;

CREATE TABLE `blog_comment` (
  `comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `comment_text` text NOT NULL,
  `comment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `parent_id` int(10) unsigned NOT NULL,
  `blog_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`comment_id`,`blog_id`),
  KEY `fk_blog_comment_blog1_idx` (`blog_id`),
  CONSTRAINT `fk_blog_comment_blog1` FOREIGN KEY (`blog_id`) REFERENCES `blog` (`blog_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

Inserting Data into Tables

Dump some data into database table – blog and blog_comment.

INSERT INTO blog (blog_id, blog_title, blog_slug, blog_text) VALUES (1,'test blog','test-blog','The topic of blogging seems to come up a lot in our social media training workshops. The benefits of a quality blog are obvious – fresh content is good for your readers and your rankings. Blogs are easy to set up and even easier to update. We often tell people that if they can use Microsoft Word… they can update a blog.rnrn                        As easy as they are to set up, they can be difficult to maintain. A good blog is filled with relevant, timely content that is updated on a regular basis. New bloggers often start out with a bang but then fizzle out when they realize that creating content can be challenging.'), (2, 'another blog', 'another-blog', 'content');
INSERT INTO blog_comment (comment_id, comment_text, comment_date, parent_id, blog_id) VALUES (1,'Test comment','2013-10-05 07:07:31',0,2),(2,'test reply','2013-10-05 07:13:06',1,2),(3,'test reply 2','2013-10-05 07:18:11',1,1),(4,'test reply 3','2013-10-05 07:20:33',2,2),(5,'Test comment','2013-10-05 10:17:20',0,1),(6,'reply','2013-10-05 10:19:28',5,1),(7,'erre','2013-10-05 10:20:57',5,1),(8,'trt','2013-10-05 10:21:39',6,1),(9,'ghgh','2013-10-05 10:24:55',8,1),(10,'Test Comment','0000-00-00 00:00:00',0,1),(11,'Test Reply','0000-00-00 00:00:00',10,1),(12,'Hello','2016-08-03 05:07:14',0,1),(13,'Hi','2016-08-03 05:07:32',12,1);

Model Class

Create a model file blogmodel.php under application/models directory with the below source code:

<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

/**
 * Description of blogmodel
 *
 * @author https://roytuts.com
 */
class BlogModel extends CI_Model {

    private $blog = 'blog';
    private $blog_comment = 'blog_comment';

    function __construct() {
        
    }

    //get blog details
    function get_blog_detail($blog_slug) {
        $query = $this->db->get_where($this->blog, array('blog_slug' => $blog_slug));
        return $query->row();
    }

    //get blog comments for blog slug
    function get_blog_comments($blog_slug) {
        $query = $this->db->query('SELECT bc.comment_id, bc.blog_id, bc.parent_id, bc.comment_text, 
                    bc.comment_date FROM ' . $this->blog_comment . ' bc, ' . $this->blog . ' b
                    WHERE bc.blog_id=b.blog_id AND 
                        b.blog_slug=' . $this->db->escape($blog_slug) .
                ' ORDER BY bc.comment_date DESC');
        if ($query->num_rows() > 0) {
            $items = array();
            foreach ($query->result() as $row) {
                $items[] = $row;
            }
            //return $items;
            $comments = $this->format_comments($items);
            return $comments;
        }
        return '<ul class="comment"></ul>';
    }

    //add blog comment
    function add_blog_comment($data) {
        $this->db->insert($this->blog_comment, $data);
        $inserted_id = $this->db->insert_id();
        if ($inserted_id > 0) {
            $query = $this->db->query('SELECT bc.comment_id, bc.blog_id, bc.parent_id, bc.comment_text, 
                    bc.comment_date
                    FROM ' . $this->blog_comment . ' bc
                    WHERE bc.comment_id=' . $inserted_id);
            return $query->result();
        }
        return NULL;
    }

    //format comments for display on blog and article
    private function format_comments($comments) {
        $html = array();
        $root_id = 0;
        foreach ($comments as $comment)
            $children[$comment->parent_id][] = $comment;

        // loop will be false if the root has no children (i.e., an empty comment!)
        $loop = !empty($children[$root_id]);

        // initializing $parent as the root
        $parent = $root_id;
        $parent_stack = array();

        // HTML wrapper for the menu (open)
        $html[] = '<ul class="comment">';

        while ($loop && ( ( $option = each($children[$parent]) ) || ( $parent > $root_id ) )) {
            if ($option === false) {
                $parent = array_pop($parent_stack);

                // HTML for comment item containing childrens (close)
                $html[] = str_repeat("t", ( count($parent_stack) + 1 ) * 2) . '</ul>';
                $html[] = str_repeat("t", ( count($parent_stack) + 1 ) * 2 - 1) . '</li>';
            } elseif (!empty($children[$option['value']->comment_id])) {
                $tab = str_repeat("t", ( count($parent_stack) + 1 ) * 2 - 1);

                // HTML for comment item containing childrens (open)
                $html[] = sprintf(
                        '%1$s<li id="li_comment_%2$s">' .
                        '%1$s%1$s<div><span class="comment_date">%3$s</span></div>' .
                        '%1$s%1$s<div style="margin-top:4px;">%4$s</div>' .
                        '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a>', $tab, // %1$s = tabulation
                        $option['value']->comment_id, //%2$s id
                        $option['value']->comment_text, // %4$s = comment
                        mysql_to_php_date($option['value']->comment_date) // %5$s = comment created_date
                );
                //$check_status = "";
                $html[] = $tab . "t" . '<ul class="comment">';

                array_push($parent_stack, $option['value']->parent_id);
                $parent = $option['value']->comment_id;
            } else {
                // HTML for comment item with no children (aka "leaf") 
                $html[] = sprintf(
                        '%1$s<li id="li_comment_%2$s">' .
                        '%1$s%1$s<div><span class="comment_date">%3$s</span></div>' .
                        '%1$s%1$s<div style="margin-top:4px;">%4$s</div>' .
                        '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a>' .
                        '%1$s</li>', str_repeat("t", ( count($parent_stack) + 1 ) * 2 - 1), // %1$s = tabulation
                        $option['value']->comment_id, //%2$s id
                        $option['value']->comment_text, // %4$s = comment
                        mysql_to_php_date($option['value']->comment_date) // %5$s = comment created_date
                );
            }
        }

        // HTML wrapper for the comment (close)
        $html[] = '</ul>';
        return implode("rn", $html);
    }

}

/* End of file blogmodel.php */
/* Location: ./application/models/blogmodel.php */

The above model class fetches blog details, all comments for a particular blog and format those comments to display nicely on the web page.

Controller Class

Create a controller file blogcontroller.php under application/controllers with the following source code:

<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

/**
 * Description of blogcontroller
 *
 * @author https://roytuts.com
 */
class BlogController extends CI_Controller {

    function __construct() {
        parent::__construct();
        $this->load->model('blogmodel', 'blog');
    }
	
    function blog_details() {
        $data['blog_detail'] = $this->blog->get_blog_detail('test-blog'); //blog slug should not be hardcoded
        $data['blog_comments'] = $this->blog->get_blog_comments('test-blog'); //blog slug should not be hardcoded
        $this->load->view('blog_details', $data);
    }

    function add_blog_comment() {
        if (isset($_POST) && isset($_POST['comment_text'])) {
            $blog_id = $_POST['content_id'];
            $parent_id = $_POST['reply_id'];
            $comment_text = $_POST['comment_text'];
            $data = array(
                'comment_text' => $comment_text,
                'parent_id' => $parent_id,
                'comment_date' => date('Y-m-d h:i:sa'),
                'blog_id' => $blog_id
            );
            $resp = $this->blog->add_blog_comment($data);
            if ($resp != NULL) {
                foreach ($resp as $row) {
                    $date = mysql_to_php_date($row->comment_date);
                    echo "<li id=\"li_comment_{$row->comment_id}\">" .
                    "<div><span class=\"comment_date\">{$date}</span></div>" .
                    "<div style=\"margin-top:4px;\">{$row->comment_text}</div>" .
                    "<a href=\"#\" class=\"reply_button\" id=\"{$row->comment_id}\">reply</a>" .
                    "</li>";
                }
            } else {
                echo 'Error in adding comment';
            }
        } else {
            echo 'Error: Please enter your comment';
        }
    }

}

/* End of file category.php */
/* Location: ./application/controllers/category.php */

Look in the above controller class, I have also formatted the comment as soon as it gets added into the database to display nicely on the web page.

View File

Create a view file blog_details.php under application/views folder.

For different levels the comments or replies shift to the right side while display on the web page.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"/>
        <title>Nested Comment using Codeigniter, MySQL, AJAX</title>
        <!--[if IE]> <script> (function() { var html5 = ("abbr,article,aside,audio,canvas,datalist,details," + "figure,footer,header,hgroup,mark,menu,meter,nav,output," + "progress,section,time,video").split(','); for (var i = 0; i < html5.length; i++) { document.createElement(html5[i]); } try { document.execCommand('BackgroundImageCache', false, true); } catch(e) {} })(); </script> <![endif]-->
        <link type="text/css" rel="stylesheet" href="<?php echo base_url(); ?>assets/css/comments.css"/>
        <script type= 'text/javascript' src="<?php echo base_url(); ?>assets/js/jquery-1.9.1.min.js"></script>
        <script type= 'text/javascript' src="<?php echo base_url(); ?>assets/js/jquery-ui-1.10.3-custom.min.js"></script>
        <script type= 'text/javascript' src="<?php echo base_url(); ?>assets/js/jquery-migrate-1.2.1.js"></script>
        <script type= 'text/javascript' src="<?php echo base_url(); ?>assets/js/jquery.blockUI.js"></script>
        <script type= 'text/javascript' src="<?php echo base_url(); ?>assets/js/comments_blog.js"></script>
    </head>
    <body>
        <div class='singlepost'>
            <div class='fullpost clearfix'>
                <div class='entry'>
                    <h1 class='post-title'>
                        <?php echo $blog_detail->blog_title; ?>
                    </h1>
                    <div> </div>
                    <div class="entry">
                        <p><?php echo $blog_detail->blog_text; ?></p>
                    </div>
                    <div> </div>
                    <div style="width: 600px;">
                        <div id="comment_wrapper">
                            <div id="comment_form_wrapper">
                                <div id="comment_resp"></div>
                                <h4>Please Leave a Reply<a href="javascript:void(0);" id="cancel-comment-reply-link">Cancel Reply</a></h4>
                                <form id="comment_form" name="comment_form" action="" method="post">
                                    <div>
                                        Comment<textarea name="comment_text" id="comment_text" rows="6"></textarea>
                                    </div>
                                    <div>
                                        <input type="hidden" name="content_id" id="content_id" value="<?php echo $blog_detail->blog_id; ?>"/>
                                        <input type="hidden" name="reply_id" id="reply_id" value=""/>
                                        <input type="hidden" name="depth_level" id="depth_level" value=""/>
                                        <input type="submit" name="comment_submit" id="comment_submit" value="Post Comment" class="button"/>
                                    </div>
                                </form>
                            </div>
                            <?php
                            echo $blog_comments;
                            ?>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

Configuring Route

Modify file application/config/routes.php file to display the comments on page load.

$route['default_controller'] = 'blogcontroller/blog_details';

JavaScript File

Create blog_comments.js file under assets/js directory with below content:

$(function () {
    $("#cancel-comment-reply-link").hide();
    $(".reply_button").live('click', function (event) {
        event.preventDefault();
        var id = $(this).attr("id");
        if ($("#li_comment_" + id).find('ul').size() > 0) {
            $("#li_comment_" + id + " ul:first").prepend($("#comment_form_wrapper"));
        } else {
            $("#li_comment_" + id).append($("#comment_form_wrapper"));
        }
        $("#reply_id").attr("value", id);
        $("#cancel-comment-reply-link").show();
    });

    $("#cancel-comment-reply-link").bind("click", function (event) {
        event.preventDefault();
        $("#reply_id").attr("value", "");
        $("#comment_wrapper").prepend($("#comment_form_wrapper"));
        $(this).hide();
    });

    $("#comment_form").bind("submit", function (event) {
        event.preventDefault();
        if ($("#comment_text").val() == "")
        {
            alert("Please enter your comment");
            return false;
        }
        $.ajax({
            type: "POST",
            url: "http://localhost/blogcontroller/add_blog_comment",
            data: $('#comment_form').serialize(),
            dataType: "html",
            beforeSend: function () {
                $('#comment_wrapper').block({
                    message: 'Please wait....',
                    css: {
                        border: 'none',
                        padding: '15px',
                        backgroundColor: '#ccc',
                        '-webkit-border-radius': '10px',
                        '-moz-border-radius': '10px'
                    },
                    overlayCSS: {
                        backgroundColor: '#ffe'
                    }
                });
            },
            success: function (comment) {
                var reply_id = $("#reply_id").val();
                if (reply_id == "") {
                    $("#comment_wrapper ul:first").prepend(comment);
                    if (comment.toLowerCase().indexOf("error") >= 0) {
                        $("#comment_resp_err").attr("value", comment);
                    }
                }
                else {
                    if ($("#li_comment_" + reply_id).find('ul').size() > 0) {
                        $("#li_comment_" + reply_id + " ul:first").prepend(comment);
                    }
                    else {
                        $("#li_comment_" + reply_id).append('<ul class="comment">' + comment + '</ul>');
                    }
                }
                $("#comment_text").attr("value", "");
                $("#reply_id").attr("value", "");
                $("#cancel-comment-reply-link").hide();
                $("#comment_wrapper").prepend($("#comment_form_wrapper"));
                $('#comment_wrapper').unblock();
            }
        });
    });
});

In the above JavaScript file, I call server side URL through AJAX. I also validate the form field.

Testing the Nested Comments App

You will get the final results in the browser:

codeigniter ajax nested comments

Now you can try to reply to the existing comments or you can create a new comment.

Download Assets

Download assets assets and put this extracted folder under project’s root folder.

Source Code

Download

2 thoughts on “Nested Comments using Codeigniter, AJAX

Leave a Reply

Your email address will not be published. Required fields are marked *