Friday, November 25, 2016

CodeIgniter CRUD Tutorial for Beginners

CRUD stands for Create Read Update Delete. In SQL, it basically represents INSERT (Create), SELECT (Read), UPDATE (Update), DELETE (Delete) commands. In this tutorial, we'll see about building a database CRUD operations using CodeIgniter and MySQL through Ajax requests. This is a continuation of the Simple login tutorial.
At the end of this tutorial you would have functional CRUD operation with user interface similar to the images bellow.
Main page
Add Student Record
Update Student Record
Delete Student Record



Aside from bootstrap v4 alpha 5, we'll also be using SweetAlert jquery plugin just to make our confirmation box more appealing and DataTable plugin for our table.

Step 1. Create Tables

We'll be creating two tables namely students and courses with one to many relationship. In real world scenario this isn't the case since a student could have multiple courses and a course can be taken by many students. Go ahead and create these tables by running the following sql queries.
CREATE TABLE `courses` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(255) NOT NULL,
 `description` TEXT NOT NULL,
 PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=4;
CREATE TABLE `students` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(255) NOT NULL,
 `course_id` BIGINT(20) NOT NULL,
 PRIMARY KEY (`id`),
 INDEX `student_courses_fk` (`course_id`),
 CONSTRAINT `student_courses_fk` FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`) ON UPDATE CASCADE
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=5;

Step 2. Create Models

We need to create models to interact with the tables which we just created. Go to "applications/models/" and create a new model then name it "Courses_model" then paste the following code.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Courses_model extends CI_Model {
 public $variable;
 public function __construct()
 {
  parent::__construct();
 }
 public function getAllCourses(){
  return $this->db->select("t1.id, t1.name, t1.description")
  ->from('courses as t1')
  ->get()
  ->result_array();
 }
}

/* End of file  */
/* Location: ./application/models/ */
"getAllCourses" function returns an array of all courses, each of which has its own id, name, and description. Next, create a model named "Students_model" and paste the following.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Students_model extends CI_Model {
 public $variable;
 public function __construct()
 {
  parent::__construct();
  $this->load->database();
 }
 public function getStudentDetail($id){
  return $this->db->select("t1.id as student_id, t1.name as student ,t2.id as course_id, t2.name as course , t2.description as course_description")
  ->from('students as t1')
  ->where("t1.id=".$id)
  ->join('courses as t2','t1.course_id=t2.id','left')
  ->get()
  ->result_array();
 }
 public function getAllStudents(){
 return $this->db->select("t1.id as student_id, t1.name as student ,t2.id as course_id, t2.name as course , t2.description as course_description")
  ->from('students as t1')
  ->join('courses as t2','t1.course_id=t2.id','left')
  ->get()
  ->result_array();
 }
 public function delete($id) {
        return $this->db->delete('students', array('ID' => $id));
    }
    public function update($id, $row) {
        $this->db->where('id', $id);
        $this->db->update('students', $row);
        return $this->db->affected_rows() > 0 ? TRUE: FALSE;
    }
    public function add($row, $dateCreated = FALSE) {
        $this->db->insert('students', $row);
        return $this->db->insert_id();
    }
}
/* End of file  */
/* Location: ./application/models/ */
The function "getStudentDetail" returns student details with an id equals "$id". We'll also be needing "getAllStudents" function to fetch all students. The first two functions basically performs "Select" database operation which is the "R" in CRUD. The "delete" function basically remove database record having an id equals "$id". To update a record we'll be calling the "update" function wherein we pass "$id" and "$row" parameters. What it does is it searches for the student id which equals the first parameter. It then updates the record with the second parameter "$row". The last function adds new record in the database student table then return the id of the newly created row.

Step 3. Create Controllers

If you followed my previous tutorial on Simple login, you don't need to create a new controller. But if you haven't, you could create your own controller or you can click here to check out my previous tutorial.
Navigate to "application/controllers/Dashboard.php" and paste the following.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Dashboard extends CI_Controller {
 public function __construct()
 {
  parent::__construct();
  if(!$this->session->userdata('user_info')['logged_in']){
   redirect(base_url('login'), 'refresh'); 
  }
  $this->load->model('students_model');
  $this->load->model('courses_model');
 }
 public function index()
 {
  $data['message']="Hello World!";
  $this->template
   ->title('Dashboard')
   ->set_layout('main')
   ->set_partial('header','partials/header',array('active'=>'home'))
      ->set_partial('footer', 'partials/footer')
      ->append_metadata('<script src="'. base_url('assets/js/workaround.js') .'"></script>')
      ->build('dashboard',$data); 
 }
 public function students(){
  $students = $this->students_model->getAllStudents();
  $courses = $this->courses_model->getAllCourses();
  $data['courses']=$courses;
  $data['students']=$students;
  $this->template
   ->title('Students')
   ->set_layout('main')
   ->set_partial('header','partials/header', array('active'=>'students'))
      ->set_partial('footer', 'partials/footer')
      ->append_metadata('<script src="'. base_url('assets/js/students.js') .'"></script>')
      ->build('students',$data); 
 }
}

/* End of file  */
/* Location: ./application/controllers/ */
If you haven't followed the last tutorial, you need to integrate Template library. You could download it from here then save it on "application/libraries/" then navigate to "application/config/autoload.php" and make sure to add Template in the auto-loaded libraries.
In order to use the functions we defined in our models we need to load the models first. You'll notice that we loaded our model inside our constructor function so as to make it available all through out our Dashboard controller class. If you want to use it in another controller, you would need to load it just like what we did on Dashboard controller.
The student variable contains all the data returned by "getAllStudents" function of our students_model. The courses variable contains the output of the "getAllCourses" function which we defined in our courses_model. We then passed it to students view. But before we proceed to our student view, you'll notice that we're including a partial named header. We're also passing a third parameter which we will be using to determine which navigation link to activate.
Go ahead and create a partial file named "header" on "application/views/partials/". Then paste the following.
<div class="masthead clearfix">
 <div class="inner">
  <h3 class="masthead-brand">Cover</h3>
  <nav class="nav nav-masthead">
   <a class="nav-link <?=$active=='home'?'active':''?>" href="<?=base_url()?>">Home</a>
   <a class="nav-link <?=$active=='students'?'active':''?>" href="<?=base_url('dashboard/students')?>">Students</a>
   <a class="nav-link" href="<?=base_url('login/logout')?>">Logout</a>
  </nav>
 </div>
</div>
I've also updated the main layout to minimize code repetition.
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>KodeMadeSimple | <?=$template['title']?></title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="<?= base_url('plugins/bootstrap_4_alpha_5/css/bootstrap.min.css') ?>">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
  <link rel="stylesheet" href="<?=base_url('assets/css/cover.css')?>">
  <link rel="stylesheet" href="<?=base_url('assets/css/custom.css')?>">
  <link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
  <script src="<?=base_url('assets/js/jquery-3.1.1.min.js')?>"></script>
  <script> var base_url = '<?= base_url() ?>'; </script>
 </head>
 <body>
  <div class="site-wrapper">
   <div class="site-wrapper-inner">
    <?php if(isset($template['partials']['header'])): ?>
    <div class="cover-container">
     <?php echo $template['partials']['header']; ?>
    </div>
    <?php endif; ?>

   <?= $template['body'];?>
   </div>
  </div>
  <?php if(isset($template['partials']['footer'])) echo $template['partials']['footer']; ?>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
  <script type="text/javascript" src="<?= base_url('plugins/bootstrap_4_alpha_5/js/tether.min.js') ?>"></script>
  <script type="text/javascript" src="<?= base_url('plugins/bootstrap_4_alpha_5/js/bootstrap.min.js') ?>"></script> 
  <?= $template['metadata']; ?>
 </body>
</html>
Since the main layout already contains the header, lets go ahead and update our view "application/views/dashboard.php"
<div class="inner cover">
 <article class="col-xs-12">
  <h1 class="cover-heading">
  <span class="text-xl">Hi</span>
  <span class="text-md"><?= $this->session->has_userdata('user_info')?$this->session->userdata('user_info')['email']:"There" ?></span> 
  </h1>
  <p>
   <?=$message ?>
  </p>
  <p class="lead">
   Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.
  </p>
  <p class="lead">
   <a class="btn btn-lg btn-secondary" href="#">Learn more</a>
  </p> 
 </article>
</div>
In addition, I've also updated the "application/assets/css/custom.css" file. You could just replace the it file with the following.
body{
 background:rgba(0,0,0,0.5);
}
body,html,.container{
 height:100vh;
}
@media (min-width: 62em){
 .masthead,.mastfoot,.cover-container{
  width:100%;
 } 
}
.masthead{
 background:#1a6f60;
 color:#fff;
 z-index:500;
}
.nav-masthead{
 display:flex;
}
.nav-masthead .active {
 border-bottom-color: #fff;
    border-bottom: 2px solid #f93;
}
.nav-masthead .nav-link:not(.active)
{
 color: #000!important;
    text-shadow: 0px -1px 1px #0bb799;
}
#students-tbl_wrapper > *{
 text-align:left;
}
#add-student{
 margin-bottom:3px; 
 width:16.2%;
}
#students-tbl_wrapper .row{
 display:flex;
 flex-direction: row;
}
#students-tbl_length{
 text-align:left;
}
#login-wrapper,#registration-wrapper{
 display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}
#login-form, #registration-form{
 background:rgba(255,255,255,0.9);
 padding:5%;
 border-radius:10px;
}
#login-wrapper,#registration-wrapper{
 background:rgba(0,0,0,0.5);
 color:#adadab;
}
#login-wrapper a,#registration-wrapper a{
 color:#000;
}
.site-wrapper-inner{
 background: #fff;
 font-family: 'Open Sans', sans-serif;
 text-shadow: none;
}
.site-wrapper-inner,.nav-masthead .nav-link{
 color:#3a3939;
}
.nav-masthead .active{
 color:#fff;
}
.text-xl{
 font-size:2em;
}
.text-md{
 font-size:1.2em;
}
.text-left,.text-left .form-group{
 text-align:left;
}
footer{
 color:#fff;
}

Step 4. Create View

We'll be displaying student details in a DataTable. DataTable is a JQuery plug-in used to enhance html table interactions. Basically, it takes away the pain of having to create functionality such as sort, search, pagination and more stuff related to html table.
Go ahead and create a view then name it "students.php" then paste the following code.
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.css"> -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.12/css/dataTables.bootstrap4.min.css">
<!-- <link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.2.2/css/buttons.bootstrap4.min.css"> -->
<div class="inner cover">
 <div class="col-xs-12">
  <button class="btn btn-md btn-success float-xs-right" id="add-student">Add</button>
 </div>
 <div class="col-xs-12">
  <table id="students-tbl" class="table table-striped table-bordered" cellspacing="0" width="100%">
   <thead>
    <tr>
     <th>Name</th>
     <th>Course</th>
     <th>Course Description</th>
     <th>Action</th>
    </tr>
   </thead>
   <tbody>
    <?php foreach((array)$students as $student): ?>
     <tr>
      <td>
       <?=$student['student']?>
      </td>
      <td>
       <?=$student['course']?>
      </td>
      <td>
       <?=$student['course_description']?>
      </td>
      <td>
       <button data-id="<?=$student['student_id']?>" class="btn btn-sm btn-danger btn-delete">Remove</button>
       <button data-id="<?=$student['student_id']?>" class="btn btn-sm btn-warning btn-edit">Edit</button>
      </td>
     </tr>
    <?php  endforeach; ?>
   </tbody>
  </table>
 </div>
</div>

<!-- Modal -->
<div class="modal fade" id="student-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
   <div class="modal-dialog" role="document">
     <div class="modal-content">
      <form id="update-form" class="text-left">
          <div class="modal-header">
           <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
           </button>
           <h4 class="modal-title" id="myModalLabel"></h4>
         </div>
         <div class="modal-body">
           <input type="hidden" name="student_id">
           <div class="form-group">
            <label for="name">Name:</label>
            <input type="text" class="form-control" id="name" name="name">
           </div>
           <div class="form-group">
            <label for="course_id">Course</label>
            <select class="form-control" name="course_id" id="course_id">
             <?php foreach($courses as $course): ?>
              <option value="<?=$course['id']?>"><?=$course['name']?></option>
             <?php endforeach; ?>
            </select>
           </div>
         </div>
    <div class="modal-footer">
     <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
     <button type="submit" class="btn btn-primary"></button>
    </div>
   </form>
     </div>
   </div>
</div>
<script src="https://cdn.datatables.net/1.10.12/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.12/js/dataTables.bootstrap4.min.js"></script>
If you checked your browser's output you'll notice that nothing happen to the table, that's because we haven't initialized our datatable yet. To do that, go ahead and create a javascript file in "application/assets/js/" and name it "students.js" then paste the following code.
$(function(){
 var Students = {};
 (function(app){
  app.init=function(){
   app.bindings();
  }
  app.bindings=function(){
   app.confirmDelete();
   app.fetchStudentData();
   app.studentFormSubmit();
   app.dataTable();
   app.formAction();
   app.clearFormErrors();
  }
  app.clearFormErrors = function(){
   $('#student-modal').on('hidden.bs.modal',function(){
    $(this).find('.form-group.has-warning').removeClass('has-warning').find('span.text-danger').remove();
   })
  }
  app.dataTable = function(){
   $('#students-tbl').DataTable();
  }
  app.formAction = function(){
   $('#add-student').on('click',function(e){
    e.preventDefault();
    $('#student-modal form').data('action','add');
    $('#student-modal form').find('button[type="submit"]').text("Add");
    $('#student-modal .modal-title').text('Add new Student');
    $('#student-modal').modal('show');
   })

  }
  app.studentFormSubmit=function(){
   $('#student-modal form').on('submit',function(e){
    e.preventDefault();
    var action = $(this).data('action');
    var $btn = $(this).find('button[type="submit"]');
    $.ajax({
     url: base_url+'ajax_student/'+action,
     type: 'POST',
     dataType: 'JSON',
     data:$(this).serialize(),
     beforeSend:function(){
      $btn.button('loading');
     },
     success:function(response){
      $('.form-group.has-warning').removeClass('has-warning').find('span.text-danger').remove();
      switch(response.status){
       case 'form-incomplete':
        $.each(response.errors, function(key,val){
         if(val.error!=''){
          $(val.field).closest('.form-group').addClass('has-warning').append(val.error);
         }
        })
       break;
       case 'success':
        var message = app.notify(response.message,'alert-success');
        $('#student-modal').modal('hide');
        $('.inner.cover').prepend(message);
        window.location.reload(true);
       break;
       case 'error':
        var message = app.notify(response.message,'alert-danger');
        $('.inner.cover').prepend(message);
       break;
      }
     },
     error: function(jqXHR,textStatus,error){
      console.log('Unable to send request!');
     }
    }).always(function(){
     $btn.button('reset');
    });
   })
  }
  app.fetchStudentData=function(){
   $('.btn-edit').on('click',function(e){
    e.preventDefault();
    var $btn = $(this);
    var id=$(this).data('id');
    $.ajax({
     url: base_url+'ajax_student/getStudent/'+id,
     type: 'GET',
     dataType: 'JSON',
     beforeSend:function(){
      $btn.button('loading');
     },
     success:function(response){
      switch(response.status){
       case 'form-incomplete':
       break;
       case 'success':
        var id = response.data['student_id'];
        var name = response.data['student'];
        var course_id = response.data['course_id'];
        $('#student-modal input[name="student_id"]').val(id);
        $('#student-modal input[name="name"]').val(name);
        $('#student-modal select[name="course_id"]').val(course_id)
        $('.modal-title').text('Update '+name);
        $('#student-modal form').find('button[type="submit"]').text("Save Changes");
        $('#student-modal form').data('action','update');
        $('#student-modal').modal('show');
       break;
       case 'error':
        var message = app.notify(response.message,'alert-danger');
        $('.inner.cover').prepend(message);
       break;
      }
     },
     error: function(jqXHR,textStatus,error){
      console.log('Unable to send request!');
     }
    }).always(function(){
     $btn.button('reset');
    });
   })
  }
  app.confirmDelete = function(){
   $(".btn-delete").on('click',function(e){
    e.preventDefault();
    var id=$(this).data('id');
    swal({
       title: 'Are you sure?',
       text: "You won't be able to revert this!",
       type: 'warning',
       showCancelButton: true,
       allowOutsideClick:true,
       confirmButtonColor: '#3085d6',
       cancelButtonColor: '#d33',
       confirmButtonText: 'Yes!'
    },function(isConfirm) {
     if(!isConfirm)
     {
      console.log('canceled');
     }
     else
     {
      $.ajax({
       url: base_url + 'ajax_student/delete/'+id,
       type : 'GET',
       dataType: 'JSON',
       success: function(response){
        switch(response.status){
         case 'success':
          swal(
              'Success!',
              response.message,
              'success'
          );
          window.location.reload(true);
         break;
         case 'error':
          swal(
              'Oops!',
              response.message,
              'error'
          );
         break;
        }
       },
       error: function(){
        swal(
            'Oops!',
            'Something went wrong.',
            'error'
        );
       }
      });
     }
    });

   })
  }
  app.notify = function(message,type){
   var response = 
     '<div class="alert '+type+' alert-dismissible fade in text-left" role="alert">'+
       '<button type="button" class="close" data-dismiss="alert" aria-label="Close">'+
         '<span aria-hidden="true">&times;</span>'+
       '</button>'+
       message+
     '</div>';
   return response;
  }
  app.init();
 })(Students);
})
We created a self invoking function which means it runs automatically without being called.
Let's take a look at "app.init()". This runs the "app.bindings()" function which contains our event handlers.
The "app.confirmDelete()" function contains the delete event handler which runs when delete button is clicked. It starts by poping a confirmation box with the help of SweetAlert plugin. If confirmed, an ajax request will be sent to "ajax_student/delete/{id}" where the "{id}" variable contains the id of the record on which the button was clicked.
The "app.fetchStudentData()" function basically fetches student data by calling "getStudent" function in our "ajax_student" controller together with a parameter "id". The modal title, action and submit button text is then updated after successful server response. Otherwise, an error notification is presented to the user.
The "app.studentFormSubmit()" handles both update and add request to the controller. It first saves the form data variable named action which is then appended to the ajax url.
In order to use the DataTable plug-in we need to initialize it. In our case it's initialized on "app.dataTable()" function. The "app.formAction()" contains the event handler adding student button. The "app.clearFormErrors()" clears all error notification in the modal every time the modal is hidden. This is to make sure that its cleaned before its going to be used for different action.

Step 5. Create Ajax Controller

Go ahead and create a controller in "application/controllers/" and name it as "ajax_student". This controller handles all our ajax requests from adding, reading, updating, and deleting a student record.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Ajax_student extends CI_Controller {

 public function __construct()
 {
  parent::__construct();
  $this->load->model('students_model');
 }
 public function getStudent($id)
 {
  if(!$this->input->is_ajax_request()){
   show_404();
  }
  if(!empty($id)){
   $response['data']=$this->students_model->getStudentDetail($id)[0];
   if($response['data']){
    $response['status']='success';
   }
   else{
    $response['status']='error';
    $response['message']='Record not found';
   }
  }
  echo json_encode($response);
 }
 public function update()
 {
  if(!$this->input->is_ajax_request()){
   show_404();
  }
  $this->form_validation->set_error_delimiters('<span class="text-danger">', '</span>');
  $this->_validate();
  $response= false;
  if(!$this->form_validation->run()){
   $response['status'] = 'form-incomplete';
   $response['errors'] = array(
    array(
     'field' => 'input[name="name"]',
     'error' => form_error('name')
    ),
    array(
     'field'=>'input[name="course_id"]',
     'error'=>form_error('course_id')
    )
   );
  }
  else{
   try{
       $this->db->trans_begin();
       $id=$this->input->post('student_id');
       $row=array(
        'name'=>$this->input->post('name'),
        'course_id'=>$this->input->post('course_id')
       );
       $this->students_model->update($id,$row);
    if(!$this->db->trans_status()){
     $this->db->trans_rollback();
     $response['status']='error';
     $response['message']='Something went wrong while saving your data';
    }else{
     $this->db->trans_commit();
     $response['status']='success';
     $response['message']='Record has been successfully updated';
    }
   }
   catch(Exception $e){
    $this->db->trans_rollback();
    $response['status']='error';
    $response['message']='Something went wrong while trying to communicate with the server.';
   }
  }
  echo json_encode($response);
 }
 public function add()
 {
  if(!$this->input->is_ajax_request()){
   show_404();
  }
  $this->form_validation->set_error_delimiters('<span class="text-danger">', '</span>');
  $this->_validate();
  $response= false;
  if(!$this->form_validation->run()){
   $response['status'] = 'form-incomplete';
   $response['errors'] = array(
    array(
     'field' => 'input[name="name"]',
     'error' => form_error('name')
    ),
    array(
     'field'=>'input[name="course_id"]',
     'error'=>form_error('course_id')
    )
   );
  }
  else{
   try{
       $this->db->trans_begin();
       $row = array(
        'name'=>$this->input->post('name'),
        'course_id'=>$this->input->post('course_id')
       );
    $this->students_model->add($row);
    if(!$this->db->trans_status()){
     $this->db->trans_rollback();
     $response['status']='error';
     $response['message']='Something went wrong while saving your data';
    }else{
     $this->db->trans_commit();
     $response['status']='success';
     $response['message']='Successfully added new record.';
    }
   }
   catch(Exception $e){
    $this->db->trans_rollback();
    $response['status']='error';
    $response['message']='Something went wrong while trying to communicate with the server.';
   }
  }
  echo json_encode($response);
 }
 public function delete($id)
 {
  if(!$this->input->is_ajax_request()){
   show_404();
  }
  $response= false;
  if(!empty($id)){
   try{
       $this->db->trans_begin();
       $this->students_model->delete($id);
    if(!$this->db->trans_status()){
     $this->db->trans_rollback();
     $response['status']='error';
     $response['message']='Something went wrong while saving your data';
    }else{
     $this->db->trans_commit();
     $response['status']='success';
     $response['message']='Successfully added new record.';
    }
   }
   catch(Exception $e){
    $this->db->trans_rollback();
    $response['status']='error';
    $response['message']='Something went wrong while trying to communicate with the server.';
   }
  }
  else{
   $response['status']='error';
   $response['message']='Record not found';
  }
  echo json_encode($response);
 }
 public function _validate(){
  $rules = array(
   array(
    'field'=>'name',
    'label'=>'Name',
    'rules'=>'required|trim'
   ),
   array(
    'field'=>'course_id',
    'label'=>'Course',
    'rules'=>'required'
   )
  );
  $this->form_validation->set_rules($rules);
 }
}

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

No comments:

Post a Comment