Introduction

Here we will see how to create nested comments using Codeigniter AJAX. 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.

Final Results

The final output on the browser would be similar to the following attached screen-shot

nested comment using codeigniter, ajax

Prerequisites

Codeigniter 3.1.10, PHP 7.0.15, Apache 2.4

Example with Source Code

We will look into the following steps in order to create nested comments using Codeigniter AJAX.

Creating Project

We will create a project root directory called ci-3.1.10-nested-comments. We will put extracted Codeingiter folders and files under the project root directory. Obviously the project context path will be ci-3.1.10-nested-comments.

Configuring Auto-load

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

app is our custom helper file. We will create it later.

We need to auto-load our database to avoid loading every time we need to use it.

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

Creating Helper – app.php

Create ci-3.1.10-nested-comments/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 we have create one function to convert MySQL date to PHP date for better readability.

Configuring Database

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

$db['default']['username'] = 'root'; //your database username
$db['default']['password'] = ''; //your database password
$db['default']['database'] = 'roytuts'; //your MySQL database name

Creating MySQL Table

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

Table – blog

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

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);

Creating Model Class

Create a model file blogmodel.php under ci-3.1.10-nested-comments/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.

Creating Controller Class

Create a controller file blogcontroller.php under ci-3.1.10-nested-comments/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, we have also format the comment as soon as it gets added into the database to display nicely on the web page.

Creating View

Create a view file blog_details.php under ci-3.1.10-nested-comments/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 ci-3.1.10-nested-comments/application/config/routes.php file.

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

Creating JavaScript File

Create blog_comments.js file under ci-3.1.10-nested-comments/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/ci-3.1.10-nested-comments/index.php/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, we call server side URL through AJAX. We also validates the form field.

Running the Application

If everything is fine then run the application. You will get the final results in the browser as I had told in Final Results section above.

Download assets assets

Source Code

That’s all. Thanks for reading.

Tags:

16 thoughts on “Nested Comments using Codeigniter, AJAX

  1. i got following error
    Parse error: syntax error, unexpected ‘li_comment_’ (T_STRING), expecting ‘,’ or ‘;’ in C:\xampp\htdocs\n\cm\application\controllers\blogcontroller.php on line 46

    how can i solve this pls help me

  2. Hello!
    Ive implemented your code within my site and get this error:

    Type: RuntimeException

    Message: Unable to locate the model you have specified: Blogmodel

    Filename: /home4/cultured/public_html/system/core/Loader.php

    Line Number: 348

    Backtrace:

    File: /home4/cultured/public_html/index.php
    Line: 315
    Function: require_once

    Ive added within my autoload and config files the necessary codes as well as added the sql within my DB. I believe I have to connect my DB with the model but dont know where. How for I change this error?

    Thank you 🙂

      1. I fixed it thank ya so very much:)

        I have another issue however, Im getting these errors:
        Message: Undefined variable
        Message: Trying to get property of non-object

        Those 2 errors represent these lines of code:
        blog_title; ?>
        blog_text; ?>
        blog_id; ?>

        Do I change anything within the M, V, or the C? The echo is not working I guess.

        Thank you for the help its greatly appreciated 🙂

Leave a Reply

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