Home Top Projects Tutorials Articles Submit Project
 
  • .NET Frameworks
  • Java Frameworks
  • PHP Frameworks
  • Ruby Frameworks
  • Other Frameworks
  • Cool AJAX sites
  • Ajax Resources
  • Ajax Tools
  • JavaScript frameworks
 
     
    • Printing in Silverlight 2 using CSS and ASP.NET AJAX 4
    • Creating an Ajax Autocompletion Textfield with Dynamic Faces
    • Creating an Autosuggest Textbox with JavaScript, Part 3
    • Create Your Own Ajax Effects
    • Simple Ajax search form that creates a formatted report as you type
    • Ajax
    • Using the Ajax control toolbox with jQuery (and ASP.NET MVC)
    • 10 Ways to Instantly Increase Your jQuery Performance
    • Ajax and XML
    • jQuery PHP Ajax Autosuggest
    Home » Tutorials » Creating a chat script with PHP and Ajax, Part 2

    Creating a chat script with PHP and Ajax, Part 2

    Welcome to the second part of the 'Creating a chat script with PHP and Ajax' series. It's been a while since the previous part, and much has happened since then. 37Signals, a very popular "web 2.0" company, has released a web application called Campfire which is actually a chat script based on Ajax and Ruby on Rails, and it has many of the features we'll be implementing in this article series.

    In this part we'll start from scratch again, but this time we'll start with a solid structure. Unlike the previous part, which was more or less a hack job, we'll start using JSON and several other libraries to make everything easier for us. I will also show you how to add a few more features, like a user list.

    This article will be quite fast paced, and some things won't be explained in detail. That's why it's highly recommended you download the projects files by clicking here so it's easier to follow along. You can also view a live demo of the project, by clicking here.

    Let's get started with defining how our web app should look like.
    The structure of our web app

    Our web app needs to be able to do several different things, like doing Ajax requests, using a MySQL database, and more. Instead of writing everything ourselves, why not use publicly available libraries?

    In our web app we're going to use the following libraries:
    - ADOdb 4.8 for database abstraction
    - Prototype 1.4.0 for all the JS wizardry
    - JSON-PHP for the JSON
    - JSON JS library for the JSON (on the JS side)
    - Lumberjack JavaScript Logger

    Next up is the structure of our web app. In this tutorial, I'll be using the following directory structure:
    - ajaxchat
     --- libs
      ----- adodb
     --- js
     --- includes

    It's obvious that all the libraries are in the 'libs' directory, and all our JavaScript files will go into the 'js' directory.

    Finally, you will also have to create a database with the following structure:
    CREATE TABLE `message` (
      `messageid` mediumint(9) NOT NULL AUTO_INCREMENT,
      `message` text NOT NULL,
      `time` int(11) NOT NULL DEFAULT '0',
      `username` varchar(255) NOT NULL DEFAULT '',
      `token` varchar(5) NOT NULL DEFAULT '',
      PRIMARY KEY  (`messageid`)
    ) TYPE=MyISAM AUTO_INCREMENT=0 ;

    CREATE TABLE `user` (
      `userid` smallint(6) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) NOT NULL DEFAULT '',
      `password` varchar(15) NOT NULL DEFAULT '',
      `lastactive` int(11) NOT NULL DEFAULT '0',
      PRIMARY KEY  (`userid`),
      UNIQUE KEY `username` (`username`)
    ) TYPE=MyISAM AUTO_INCREMENT=0 ;

    Before writing the main thing of our web app (the chat), let's first create a few basic necessities, like a database config file, and a few other things.
    The first thing our web app needs is a 'global.php' file, which will setup a database connection, include all the other files and libraries and do a few other basic tasks. It looks like this:
    <?php
    error_reporting (E_ALL);

    if (get_magic_quotes_gpc()) {
        function stripslashes_deep($value)
        {
            $value = is_array($value) ?
                        array_map('stripslashes_deep', $value) :
                        stripslashes($value);

            return $value;
        }

        $_POST = array_map('stripslashes_deep', $_POST);
        $_GET = array_map('stripslashes_deep', $_GET);
        $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
            $_REQUEST = array_map('stripslashes_deep', $_REQUEST);
    }

    // Get site path
    $site_path = realpath(dirname(__FILE__) . '/../') . '/';

    // Include libraries
    include ($site_path . 'libs/JSON.php');
    include ($site_path . 'libs/adodb/adodb.inc.php');

    include ($site_path . 'includes/functions.php');
    include ($site_path . 'includes/user-functions.php');
    include ($site_path . 'includes/chat-functions.php');

    // Connect to MySQL DB
    $db = NewADOConnection('mysql');
    $db->Connect('localhost', 'DB_USER_HERE', 'DB_PASSWORD', 'DB_NAME');
    $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

    ?>

    Make sure to put your own database user, password and name in the file above, and save it as 'global.php' in the includes directory. The web app will also need a common 'functions.php', which holds frequently used functions like generate_password() and such. Click here to download the 'functions.php' and save it in the includes directory.

    Now that we've got the basics pretty much laid out, let's start with the actual web app. The first thing we'll do is create the chat client itself.


    Creating the chat client

    To start off, our chat client only needs to do three things:
    1. Let the visitor enter a username and login
    2. Show all the incoming messages, and allow new messages to be sent
    3. Keep a list of who's in the chat room

    The HTML behind these three things is fairly standard:
    <html>
            <head>
                    <title>PHP-Based Live Ajax Chat</title>

                    <link rel="stylesheet" href="style.css" type="text/css" media="screen" />

                    <script type="text/javascript">
                            var base_url = document.location.protocol + '//' + document.location.host + document.location.pathname;
                            var username = '';
                            var password = '';
                            var lastactive = '';
                            var dup_filter = new Array();
                    </script>

                    <!-- JS Libraries -->
                    <script type="text/javascript" src="libs/prototype.js"></script>
                    <script type="text/javascript" src="libs/json.js"></script>
                    <script type="text/javascript" src="libs/logger.js"></script>
                    <script type="text/javascript" src="js/common.js"></script>

                    <!-- Chat JS Files -->
                    <script type="text/javascript" src="js/user.js"></script>
                    <script type="text/javascript" src="js/ping.js"></script>
                    <script type="text/javascript" src="js/chat.js"></script>

            </head>

            <body>

            <div id="login">
                    <h1>Enter a username to enter the chatroom...</h1>
                    <label for="username">Username: </label>
                    <input type="text" name="username" id="username" />

                    <a href="#" onclick="login();">Enter the chat room &raquo;</a>
            </div>

            <div id="chat" style="display:none;">
                    <div id="messages">
                            <b><i>Welcome to the chatroom...</i></b>
                            <!-- Messages will be added here -->
                    </div>
                  
                    <div id="userlist">
                            <h2>Users:</h2>
                            <ul id="users">
                                    <!-- Users will go here -->
                            </ul>
                    </div>

                  

                    <input type="text" id="talk" name="talk"><input type="submit" value="Say It!" onclick="send_message();" id="sayit" />

            </div>

            </body>
    </html>

    The above page uses fairly basic HTML, and consists mainly of two div elements ("login" and "chat"), a few input elements and a list element for the user list. The page also includes several JavaScript files, including the Prototype, JSON and Logger libraries. To make the chat room look a bit prettier, an external stylesheet is used, which can be downloaded by clicking here.

    If you try to use the page above, you'll find that nothing works yet, because we haven't written any of our custom JS files yet. But that's what we'll do now, starting with the login.


    Creating the login

    If you have a look at the HTML in the previous example, you will notice that the login link uses a function called 'login()'. This function has to send the username that was filled in by the visitor to the server, using an Ajax request.

    It looks like this:
    function login() {
            var username = $F('username');

            // Check if username isn't empty
            if (username == '')     {
                    alert('Please enter a username');
                    return false;
            }

            // Send login request
            var url = base_url + 'login.php';
            var pars = 'username=' + escape(username);

            var ajax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: login_handle_response});

            Logger.info("Logged in: " + pars);
    }

    The function makes sure a username was filled in, and then sends the request to 'login.php'. For easy debugging it also logs the information using the Logger library.

    On the server side, PHP has to handle this request, and return the appropriate response. What the login.php file has to do is make sure that username isn't already in use by someone else, then insert the new user, and finally return the user list. It looks like this:
    <?php
    include ('includes/global.php');

    // Get username and generate last active and random password
    $username = r('username');
    $lastactive = time();
    $password = generate_password(15);

    // No username?
    if (empty($username) == true) {
            return_error('Please fill in a username', 10);
    }

    // Make it safe
    $username = htmlentities($username);

    // Remove any inactive users
    remove_inactive_users();

    // Try to insert new user
    $ok = $db->execute ("INSERT INTO `user` (username, lastactive, password) VALUES (?, ?, ?)", array($username, $lastactive, $password));

    // Unique error? (errorno = 1062)
    if ($db->ErrorNo() == 1062) {
            return_error ('Another user is already using this username. Please pick another one.', 20);
    }

    // Return success, by sending the complete userlist
    $return = array('success' => true, 'action' => 'login', 'username' => $username, 'lastactive' => $lastactive, 'password' => $password);

    // Get userlist
    $userlist = $db->GetAll("SELECT userid, username FROM `user`");
    $return['userlist'] = $userlist;

    // Return JSON
    return_raw_json($return);

    ?>

    Most of the above is fairly self-explanatory, and should be easy to understand. Please note that the remove_inactive_users() function is used to remove any inactive users, and can be found in the user-functions.php file, which can be downloaded by clicking here, and should be stored in the includes directory.

    At the end of the example above the return_raw_json() function is used to return the user list to the chat client. This function can be found in the common functions.php file, and uses the PHP-JSON library to send the JSON.

    Now that login.php has sent a response, the chat client has to handle this. When sending the original request, we actually defined a feedback handler called login_handle_response, which looks like this:
    function login_handle_response(request) {
            var data = request.responseText;

            // Transform JSON
            data = JSON.parse(data);

            if (data == false) {
                    // Not JSON
                    alert('An unexpected error happened. Please try again');
                    return false;
            }

            if (typeof(data.error) != 'undefined') {
                    alert(data.error.msg);
                    return false;
            }

            // Which action?
            if (data.action == 'login' && data.success == true)     {
                    do_login(data);
            }
    }

    This is just a really basic function to transform the returned JSON into a native JS object, and then calls the do_login() function to continue the login process.

    The do_login() function takes in the data returned by login.php, shows the chat, and creates the user list. It looks like this:
    function do_login(data) {
            // Are password and lastactive there?
            if (typeof(data.lastactive) == 'undefined' || typeof(data.password) == 'undefined')     {
                    alert('An unexpected error happened. Please try again');
                    return false;
            }

            // Save them
            password = data.password;
            lastactive = data.lastactive;
            username = data.username;

            // Insert users into userlist
            add_userlist(data.userlist);

            // Show chat
            Element.hide('login');
            var el = $('chat');
            el.style.display = 'block';

            var ping_obj = new PeriodicalExecuter(ping, 1);
    }

    A very important thing which the do_login() function does is creating a new 'PeriodicalExecuter' object, which is the whole core behind our chat. It's basically a timer that runs the ping() function every second, and that's the function which gets the new messages in.

    The do_login() function also uses the add_userlist function, which is used to create the user list and looks like this:
    function add_userlist(list) {
            var ul_users = $('users');
            ul_users.innerHTML = '';

            list.each( function(user) {
                    var new_li = document.createElement('LI');
                    new_li.id = 'user-' + user.userid;
                    new_li.innerHTML = user.username;

                    ul_users.appendChild(new_li);
            });
    }

    That's pretty much it for the login part, and all we need to write now is the code necessary to get new messages and any changes in the userlist.
    Ping? Pong!

    As I've already explained, a special function called ping() will run every second, and this is the function that will:
    - let the chat server know that the user is still in the chatroom
    - request the updated user list
    - request any new messages

    It's actually a very simple function, and all it really does is send a Ajax request, like so:
    function ping() {
            // Send ping, to let the server know I'm still here
            var url = base_url + 'ping.php';
            var pars = creds();

            var ajax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: handle_ping});

            Logger.info("Sent Ping: " + pars);
    }

    This request is then received by the ping.php file, which actually does all the magic. It first authenticates the user, like so:
    <?php
    include ('includes/global.php');

    // Authenticate user
    $user = auth_user();

    Using the auth_user() function, which is located in 'includes/user-functions.php' and looks like this:

    function auth_user {
            global $db;

            // Get vars
            $username = r('username');
            $password = r('password');
            $lastactive = r('lastactive');

            if (empty($username) OR empty($password) OR empty($lastactive)) {
                    return_error('Invalid user. Please logout and try again.', 10);
                    die();
            }

            // Authenticate user
            $user = $db->GetRow("SELECT userid, username, password, lastactive FROM user WHERE username = ? AND password = ?", array($username, $password));

            if ($user == false) {
                    return_error('Invalid user. Please logout and try again.', 10);
                    die();
            }

            return $user;
    }

    As you can see, the auth_user() function is a really basic function that simply checks the username and password of the user.

    The ping.php file then continues to send the new user list, like so:
    // Send new user list
    send_userlist();

    Again using a separate function, called send_userlist(), found in includes/user-functions.php, which looks like this:
    function send_userlist() {
            global $db;

            // Remove any inactive users (no ping for 15 seconds)
            remove_inactive_users();

            // Get userlist
            $userlist = $db->GetAll("SELECT userid, username FROM `user`");

            // Queue command
            json_queue_add ('add_userlist', array('userlist' => $userlist));

            return true;
    }

    The most important part in this function is the second last line, on which the json_queue_add() function is used. This is a special function that is used to queue different JSON commands, with different arguments, and when the json_queue_send() function is called, all the JSON commands are sent at once. This allows us to send multiple instructions back to the client at once. These functions look like this:
    function json_queue_add ($func, $args) {
            $funcs =& $GLOBALS['_json_queue'];

            // Add to commands array
            $funcs[] = array('func' => $func, 'args' => $args);
    }

    function json_queue_send {
            $funcs =& $GLOBALS['_json_queue'];
            ob_clean_all();

            $data = array('queue' => $funcs);

            return_raw_json($data);
    }

    Getting back to the ping.php file, after sending the new user list, it sends any new messages, using the send_messages() function:
    function send_messages($user) {
            global $db;

            $lastactive = $user['lastactive'];

            // Get all new messages since last active
            $messages = $db->GetAll("SELECT username, time, message, token FROM message WHERE time >= ?", array($lastactive));

            if ($messages == false) { return false; }

            // Queue command
            json_queue_add ('add_messages', array('messages' => $messages));
    }

    It then updates the last active timestamp of the user, and finally sends all the commands with the json_queue_send() function.

    Now that the ping.php file has sent all the commands, it's back to the JS file, as it needs to handle the response.

    The handle_ping() function, which is used as the callback for the request (see the ping() function), begins with the basic stuff:
    function handle_ping (request) {
            var data = request.responseText;

            Logger.info("Ping Received: " + data);

            // Transform JSON
            data = JSON.parse(data);

            if (data == false) {
                    // Not JSON
                    Logger.error("Invalid Response", "Not a JSON structure");
                    return false;
            }

            if (typeof(data.error) != 'undefined') {
                    alert (data.error.msg);
                    return false;
            }

            if (typeof(data.queue) == 'undefined') {
                    Logger.error("Invalid Response", "Not a queue");
                    return false;
            }

    As you can see in the above example, it makes sure that a valid JSON response has been sent, and that an actual command queue was sent.

    After that the function needs to handle all the different commands. Seeing as we only have three commands yet, it's rather simple:
    data.queue.each( function(item) {
                    var func = item.func;
                    var args = item.args;

                    // Determine function
                    switch(func) {
                            case 'add_userlist':
                                    add_userlist (args.userlist);
                                    break;
                            case 'update_lastactive':
                                    lastactive = args.lastactive;
                                    break;
                            case 'add_messages':
                                    add_messages (args.messages);
                                    break;
                    }
            });
    }

    We loop through the queue, and use a switch statement to check which command we're dealing with. In each case, we do something different, for example when we're dealing with the 'update_lastactive' command, the lastactive variable is set to the updated value.

    In the above example you will see that a function called 'add_messages' is used to add any new messages. This function looks like this:
    function add_messages(messages) {
            var div = $('messages');

            messages.each( function(message) {
                    // Make sure to filter out duplicate messages
                    var dup = message.time + '|' + message.token;
                    if (dup_filter.inArray(dup) == true) { return; }
                    add_dup_filter(dup);
                  
                    var new_b = document.createElement('b');
                    new_b.innerHTML = message.username + '> ';
                    div.appendChild(new_b);

                    var new_span = document.createElement('span');
                    new_span.innerHTML = message.message.escapeHTML();
                    div.appendChild(new_span);

                    div.appendChild(document.createElement('br'));
            });

            div.scrollTop = div.scrollHeight;
    }

    It basically loops through all the new messages, and then adds them (by inserting new HTML elements in the chat div). But before adding them, a check is made to make sure duplicate messages aren't added.

    Because of the way Ajax and the internet works, it's very easy to receive the same new messages multiple times, which means they'll show up multiple times. To prevent this from happening, each message has a unique token, and this is used to make sure that duplicate messages aren't added twice.

    This is basically how everything in the chat room is constantly updated, by sending a ping request every second, and then receiving new data back. Let's have a look at the final part of the chat room: sending messages.


    Sending chat messages

    To send a new message, a function called 'send_message()' is used, and this function sends the message to the chat server, using an Ajax request, like so:
    function send_message() {
            var talk = $('talk');
            var msg = talk.value;

            if (msg == '') {
                    return false;
            }

            // Send message
            var url = base_url + 'chat.php';
            var pars = creds() + '&message=' + escape(msg);

            var ajax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: handle_ping});

            Logger.info("Sent message: " + pars);

            // Clear field
            talk.value = '';
    }

    The function first gets the value of the message field, and makes sure it isn't empty. After that it sends the message, and the user's credentials, to the chat.php file, using an Ajax request.

    The chat.php file only does two things: checks the user credentials and adds the new message. Checking the user credentials is extremely easy, as we've already got a function for that: auth_user() (see the previous section).

    If the user credentials are okay, the chat.php file then uses the add_message() function to add a message, and this function looks like this:
    function add_message ($user) {
            global $db;

            // Get vars
            $message = r('message');
            $username = $user['username'];
            $time = time();
            $token = generate_password(5);

            if (empty($message)) { return false; }

            // Insert message
            $ok = $db->execute ("INSERT INTO message (username, time, message, token) VALUES (?, ?, ?, ?)", array($username, $time, $message, $token));

            // All done
            return true;
    }

    It's pretty much a basic database insert, and there's nothing spectacular about it. Note how we generate a random token, to prevent the duplicate message problem, which I talked about in the previous section.
    That's it!

    I've taken you pretty much through everything in the Ajax/PHP chat, and I've shown you the most important parts.

    It's very understandable if you had a hard time following this article, as "the glue" between all the parts is still pretty much missing. That's why you can click here to download the complete Ajax/PHP chat.

    Make sure to open includes/global.php and change your MySQL details, so that they are correct. Also make sure you've already created the database, and setup the right structure.

    If you want to give the chat a test run, click here to view a live demo right here on PHPit.net.

    source: learnphp

    In the next part of this article series, we'll add some more features and improve our chat by making it more stable. Some of the new things we'll be adding are registered users, administrators, and a silenced chat room.

    Copyrights Reserved AjaxProjects.com 2006-2013, Powered by Enozom - Mobile Development Company -Privacy Policy