Load More Using KnockoutJS, PHP/CodeIgniter (Part 1)

Everyone has used social networking sites and almost all of them implement Load More functionality to load additional posts/messages. Load More is actually alternate way of pagination.

There are two kinds of Load More functionality. First is to load additional content when the user hits the scrollbar at the bottom of the page. Another kind is to load new content only when user clicks on “Load More” button. Personally I prefer the second one because it reduces the load on server and load content only when user wants it and in this article we will take a look at implementing this second kind.

Other Parts

In CRUD Using jQuery and CodeIgniter series of articles, I explained very basics of jQuery and making use of Ajax calls. There was no proper architecture followed in the code which I wrote in those tutorials. But in this tutorial you will learn how to properly structure your application using MVC and MVVM architectures.

Demo Source Files

Prerequisites

  1. Basics of OOP with JavaScript, and Knockout.js
  2. MVC and MVVM Architectures
  3. PHP/CodeIgniter

Using the above technologies, we will build a social network like Load More experience for displaying messages.

Setting up the Database

Let’s create a test database and a msgs table from which we will load the records to display in View.

CREATE DATABASE `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
 
CREATE TABLE `test`.`msgs` (
`msg_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`msg_txt` TEXT NOT NULL,
`msg_by` VARCHAR( 100 ) NOT NULL,
`msg_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE = InnoDB;

Download the CodeIgniter source and extract it in a folder load_more. Copy this folder in the www directory of Apache and open config.php from load_more/application/config directory and I modified the value of few indexes of $config array.

//set the base URI of our web app
$config['base_url'] = 'http://localhost/load_more/';
 
//remove index.php
$config['index_page'] = '';

Now open database.php in the same folder and modify following lines:

$db['default']['username'] = 'root';
$db['default']['password'] = 'SET YOUR PASSWORD HERE';
$db['default']['database'] = 'test';

Open autoload.php and modify following line:

//auto load url helper
$autoload['helper'] = array( 'url' );

Open routes.php and modify the default home controller from welcome to msgs.

$route['default_controller'] = "msgs";

Open contants.php and add a new constant MSG_PAGE_SIZE which will be used to get the total messages at a time.

define( 'MSG_PAGE_SIZE', 2 );

Finally create an htaccess file in load_more folder to make your URLs human friendly. Paste the following code in the newly created htaccess file.


RewriteEngine On
RewriteBase /load_more

RewriteCond %{REQUEST_URI} ^system.*
RewriteRule ^(.*)$ /index.php?/$1 [L]

RewriteCond %{REQUEST_URI} ^application.*
RewriteRule ^(.*)$ /index.php?/$1 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]


ErrorDocument 404 /index.php

Above code is taken from CodeIgniter’s wiki (comments removed to make code short). Note that the second line contains /load_more in it. This is the name of the folder in which our source files reside.

This is the best htaccess file for making URLs friendly in CodeIgniter. Before expecting this to work. Make sure you have enabled mod_rewrite module in Apache.

Create Model

Now create a model class Msgs_m which will interact with the database.

class Msgs_m extends CI_Model {
 
   function __construct() {
      parent::__construct();
 
      //load CodeIgniter’s database class
      $this->load->database();
   }
 
} //end class

Create two methods in it. First method will get the messages according to the last message’s date and size (No of records you want to get at a time).

public function getMsgs( $lastMsgDate, $pageSize = MSG_PAGE_SIZE ) {
   $query = "";
   
   try {
       $query = "SELECT m.msg_id, m.msg_by, m.msg_txt, m.msg_date
                   FROM msgs m
                  WHERE m.msg_date < ?
               ORDER BY m.msg_date DESC
                  LIMIT ?";
       
       $res = $this->db->query( $query, array( $lastMsgDate, $pageSize ) );
       
       if( $res->num_rows() == 0 )
           return NULL;
       else
           return $res->result();
       
   } catch( Exception $ex ) {
       //it's better to write exception in log file or db
       return NULL;
   }
}

Above method sets default argument so if no argument is provided for pageSize, it will automatically set it to MSG_PAGE_SIZE (which we defined in contants.php).

The query is also straight forward. It gets all the messages which are older than the lastMsgDate. lastMsgDate is the timestamp of last message shown at the front-end and this timestamp will be passed from front-end. On first page load, an argument of current date will be used to load messages. The LIMIT clause is used to get the number of records we want to fetch at a time.

Note that ? (question mark) is used in place of parameters in query. This ? will be replaced with the actual values later when we execute the query using $this->db->query(). The second parameter in query() method is an array in which we pass the lastMsgDate and pageSize. The question mark is used instead of directly embedding the value in query is called query binding and it’s done to automate the string escaping process to prevent SQL injection attacks.

Here’s our second method that will go in Msgs_m model. This method will get the date of first message entered in msgs table.

public function minMsgDate() {
    try {
        $query = "SELECT min(m.msg_date) as msg_date FROM msgs m";
 
        $res = $this->db->query( $query );
        $row = $res->row();
        return $row->msg_date;
    } catch( Exception $ex ) {
        //it's better to write exception in log file or db
        return '01-01-01';
    }
}

Again nothing special going on here. A simple query which gets smallest date. In case of exception we return some dummy date.

Create Controller

Create a new controller Msgs with a constructor which will load Msgs_m model and CodeIgniter’s security helper.

class Msgs extends CI_Controller {
   public function __construct() {
       parent::__construct();
       $this->load->model( 'Msgs_m' );
       $this->load->helper( 'security' );
   }
} //end class

Add a new index method in the above controller.

public function index()
{
    $minMsgDate = $this->Msgs_m->minMsgDate();
 
    $data = array( 'msgs' => json_encode(
                      $this->Msgs_m->getMsgs( date( 'Y-m-d H:i:s', time() ) ) 
                   )
                 , 'minMsgDate' => $minMsgDate );
 
    $this->load->view( 'msgs', $data );
}

Above method calls minMsgDate() to get the date of first message added. Then getMsgs() is called with current date passed to get the latest messages added in the table. The array returned from getMsgs() is encoded in JSON and set as msgs to the first index of $data array. Then $minMsgDate is added to the second index of $data. Then msgs view is loaded with $data passed to it.

Add another method getMsgs() in the controller.

public function getMsgs() {
    if( $_SERVER['REQUEST_METHOD'] == "POST" ) {
        $lastMsgDate = $this->security->xss_clean( $_POST[ 'lastMsgDate' ] );
    }
    else return;
 
    echo json_encode( $this->Msgs_m->getMsgs( $lastMsgDate ) );
}

Above method will not work (return control) if request is not made using POST method. This method will be called from JavaScript using Ajax call to get the next set of messages to display using date of last shown message. It simply generates JSON of messages returned from Msgs_m->getMsgs.

Create View

Create a msgs view and add following markup in it.

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Load More Demo by iFadey</title>
 
   <base href="<?php echo base_url() ?>" />
 
   <link href="css/style.css" rel="stylesheet">
</head>
<body>
 
<div id="container">
   <h1>Load More Demo by iFadey</h1>
 
   <div id="body">
      <p style="display: none">No Message Found :(</p>
      <button id="moreBtn" style="display: none">more messages...</button>
   </div><!--end body-->
 
</div><!--end container-->
</body>
</html>

Msg Template

With in the div#body (right above p element with text “No Message Found”), add the following code.

<ul class="msgs">
   <li>
      <!--user who posted the message-->
      <div class="by">
         Posted by:
         <span></span>
      </div>
 
      <!--date on which message is posted-->
      <div class="date">
         Posted On:
         <span></span>
      </div>
 
      <!--actual message-->
      <div class="txt"></div>
   </li>
</ul>

Above code will be used as a template for displaying messages which we get from server.

Msg Class

Following class will be used to map each record from server to JavaScript. Each record will be an instance of this class. Add following script before closing tag of body.

/****** custom Msg class for mapping ******/
function Msg( msgBy, msgDate, msgTxt ) {
    this.msgBy = msgBy;
    this.msgTxt = msgTxt;
 
    var tmpDate = Utils.parseMySqlDate( msgDate );
    this.msgDate = tmpDate.getDate() + '/' + (tmpDate.getMonth() + 1) + '/' + tmpDate.getFullYear();
}

In JavaScript, classes are defined like functions. All the properties defined (using this keyword) within the Msg constructor are public data members. So we will bind the value of msgBy, msgTxt, and msgDate data members to display in the view.

Note how I formatted the msgDate. First JavaScript Date object is returned from parseMySqlDate method. This method parses MySQL timestamp and returns a JavaScript’s Date object. Then I used Date object’s getDate, getMonth, and getFullYear methods to format date in human readable format.

For more info on parsing MySQL timestamp, check my previous post on Common JavaScript Date Operations.

Data Binding

Let’s bind public data members of Msg class to template we created above to show the messages in unordered list. Here’s the bounded version of above list item.

<li>
   <!--user who posted the message-->
   <div class="by">
      Posted by:
      <span data-bind="text: msgBy"></span>
   </div>
 
   <!--date on which message is posted-->
   <div class="date">
      Posted On:
      <span data-bind="text: msgDate"></span>
   </div>
 
   <!--actual message-->
   <div class="txt" data-bind="text: msgTxt"></div>
</li>

Note the addition of data-bind attribute in two span elements and in last div element having class txt. This data-bind attribute tells Knockout to bind the text (textContent or innerText in IE8 and below) property of element with the specified property. For example data-bind=”text: msgTxt” means bind the text inside div element to msgTxt property of Msg class.

Now I will add a loop on ul element which will iterate over array of multiple Msg class objects. Let’s name this array msgs which I will add later in ViewModel class.

<ul data-bind="foreach: msgs" class="msgs">
   <!-- here goes the bounded li element as shown above -->
</ul>

Knockout’s foreach binding which iterate over msgs array will replicate the code inside ul element (here it will replicate li element). Knockout tries to find all the properties (msgBy, msgDate, msgTxt) which we bounded in li element in the object which is currently being iterated (the current msg object in msgs array).

Now here’s the binding of some attributes of p element (No Message Found) and #moreBtn as shown below.

<p data-bind="visible: noMsgVisible" style="display: none">No Message Found :(</p>
 
<button id="moreBtn" data-bind="click: moreBtn.loadMsgs, visible: moreBtn.visible, disable: moreBtn.disable">more messages...</button>

Only visible attribute of p element is bound to noMsgVisible property of ViewModel which is boolean observable. It hides the message when noMsgVisible is set to true and vice versa.

Three attributes of #moreBtn are binded with the ViewModel properties which I will define later. First is the click binding. It tells Knockout to run loadMsgs method when user clicks on this button.

Second is the visible binding. moreBtn.visible will be a computed observable which will return true/false based on some condition (discussed later). According to the condition’s result, it will display/hide the moreBtn.

Third and last is the disable binding. The moreBtn.disable is the observable property which disables this button when set to true and vice versa.

ViewModel

In KnockoutJS, ViewModel is an object which contain data members storing value of different attributes of element/control in View. These data members can be observable, observableArray, computed, event handlers or even JavaScript variables with primitive data types. Let’s define MsgViewModel class for our above View.

function MsgsViewModel() {
        //-----private variables-----//
        var self = this
          , _pageSize = <?php echo MSG_PAGE_SIZE ?>
          , _msgs = <?php echo $msgs ?>;
} //end MsgsViewModel

In MsgViewModel, first I defined three private variables which will be used later. Variable self will point to MsgsViewModel object when instantiated using new keyword ( new MsgsViewModel() ).
Then _pageSize contains the maximum number of records we want to fetch at a time. Remember MSG_PAGE_SIZE is a constant which we defined in contants.php.
$msgs contains an array of messages which the controller fetched using Msgs_m model’s getMsgs method and converted it in json format so we can use it as JavaScript array. So _msgs will contain an array of initial set of messages which we want to display on first page load.

After private variables, let’s define some public observable data members.

//---------observables---------//
self.noMsgVisible = ko.observable( false );

First observable is for p element containing “No Message Found”. It’s set to false because initially we want to hide this message.

if( _msgs !== null )
   self.msgs = ko.observableArray( ko.utils.arrayMap( _msgs, function( msg ) {
      return new Msg( msg.msg_by, msg.msg_date, msg.msg_txt );
   }) );
else {
   self.msgs = ko.observableArray( [] );
   self.noMsgVisible( true );
}

When no message is fetched from database, private variable _msgs will be null. So if _msgs is null then a blank array is passed to observableArray and noMsgVisible is set to true so user can see a message “No Message Found”. Otherwise public property msgs of type observableArray is created from private _msgs JavaScript array. ko.utils.arrayMap is Knockout’s method which is used to map each record in _msgs array to Msg object and return a new array of Msg objects. First parameter to arrayMap method is _msgs array on which it will iterate and second parameter is a function which will run for each object in _msgs array. This function simply create a new instance of Msg class and returns it. Then arrayMap method add these newly returned objects from this function to new array which it will return. This newly returned array from arrayMap is added in observableArray and assigned to msgs property. Now remember that in View, we used foreach loop on msgs array which is a public property of MsgsViewModel.

Now you will start to realize that all the bindings I did in View are actually done with MsgViewModel properties.

Right after self.msgs array, create some properties for #moreBtn. If you remember, three attributes of moreBtn were bounded i.e. visible, disable, and click. Here I will group them in a single object.

self.moreBtn = function() {
   var _lastMsgDate = ko.observable( Utils.parseMySqlDate( _msgs[ _msgs.length - 1 ].msg_date ) );
   var _minMsgDate = Utils.parseMySqlDate( '<?php echo $minMsgDate ?>' );
 
   return {};
}(); //note parenthesis here used to execute function

Note that self executing anonymous function is used which currently returns a blank object { }. This function is actually a closure because it define two variables and will use them later in returned object.

_lastMsgDate contains the posted date of last message in the array. Note Utils.parseMySqlDate method converts MySQL timestamp string to JavaScript Date object and pass it to observable.

_minMsgDate is the posted date of first message. $minMsgDate was set in controller using minMsgDate() method of Msgs_m model.

Now define the visible property in the returning object from this function.

return {
   visible: ko.computed( function() {
      return ( _lastMsgDate() > _minMsgDate );
   }, self.moreBtn )
};

visible property is a computed observable and its value will be whatever the function passed as parameter returns. Here the function returns true (show) or false (hide) on the basis of condition _lastMsgDate is greater than _minMsgDate. This condition actually means that hide the #moreBtn when the posted date of last message in the record set is equal or less than than posted date of first message added in the table.

Now add disable property after visible.

disable: ko.observable( false )

This is simply an observable with false passed as default because we don’t want the #moreBtn to be disabled by default.

The last thing in returning object is the method which will execute when user will click on #moreBtn. This method will get the next set of records from the server using Ajax call.

loadMsgs: function() {
    self.moreBtn.disable( true );
 
    $.ajax({
        url: "msgs/getMsgs",
        type: "POST",
        data: { lastMsgDate: Utils.getMySqlTimestamp( _lastMsgDate() ) },
        dataType: "json"
    }).success( function( response ) {
        self.moreBtn.disable( false );
    });
} //end loadMsgs

Initially this method will disable the #moreBtn so when it’s executing, user won’t be able to press the button again.

Second thing which this method do is make an Ajax call to msgs/getMsgs controller’s method which returns next set of messages on the basis of _lastMsgDate.

Note the function which is passed to success method of Ajax call. Parameter response contain records in json format returned from server. Within this function I enabled the #moreBtn again so user can click it again when response is returned from server. I highly recommend to handle error case as well when making Ajax calls.

But before enabling #moreBtn, we need to iterate over response array and add each object in that array to our msgs observableArray as shown below.

if( response !== null ) {
   for( var i = 0; response[ i ]; ++i )
      self.msgs.push( new Msg( response[ i ].msg_by
                             , response[ i ].msg_date
                             , response[ i ].msg_txt ) );
 
   _lastMsgDate( Utils.parseMySqlDate( response[ response.length - 1 ].msg_date ) );
}
 
self.moreBtn.disable( false );

Add above code in function passed to success. Each object in response array is first mapped to Msg class and then added to msgs observableArray. After the loop, _lastMsgDate is updated from the last message’s posted date.

Initializing ViewModel

Our last step is to create new instance of MsgViewModel and apply all the bindings with the View.

$( function() {
   ko.applyBindings( new MsgsViewModel() );
});

Within jQuery’s DOM Ready, Knockout’s applyBindings method is called to attach/link MsgViewModel with the View.

Upcoming Part 2

In Part 2 of this tutorial, I will be showing you how to load newly added messages automatically just like in Twitter. For that I will be using HTML5′s WebSocket and some socket programming using PHP or Node.js (I haven’t decided yet). This will be one step forward from Ajax and you will learn how to monitor newly added messages without adding much load on server.


comments powered by Disqus