Building a jQuery/PHP Powered Chat Room

Chris Coyier //

The Plan

In this tutorial we are going to build a little chat program that is really easy to get up and running on any server running PHP. No database is required - as the chat will store itself in a simple text file. The technologies used:

  • PHP - Will handle all the server side stuff
    • Writing new messages to the text file
    • Reading out new messages from the text file
    • Retrieving the "state" of the text file
    • Basic security
  • jQuery/JavaScript - Will handle the client side stuff. This is an AJAX-y application, meaning that messages pop onto the screen (both yours and others) without needing any page refresh.

    • Periodically asking the server if there are new messages that have been posted
    • Appending new messages to the chat
    • Scrolling the chat down to the most recent messages
    • Asking and setting the user name
    • Limiting the text input to prevent gigantic ridiculous messages
    • Basic security
  • Text File - Stores the chat

NOT The Plan

This tutorial covers all kinds of interesting things and interesting technology and the end result is definitely a chat room. What it isn't is the World's Most Full-Featured Chat Room. You can't kick people out or ban people. People can potentially have the same name. You can't see all the current members of the chat. In other words, this ain't IRC. It's just a fun demo and totally workable in a number of situations. If you want to take this and run with it and make it more full featured, be our guest!

Basic HTML Structure

<div id="page-wrap">

    <h2>jQuery/PHP Chat</h2>
    
    <p id="name-area"></p>
    
    <div id="chat-wrap"><div id="chat-area"></div></div>
    
    <form id="send-message-area">
        <p>Your message: </p>
        <textarea id="sendie" maxlength = '100'></textarea>
    </form>

</div>

Precious little markup here folks. Even what you are looking at above is 50% unrelated to the specific functionality of this tutorial. The page-wrap is to center things. The double-divs thing going on with the chat-wrap and chat-area is just to pull off the totally unnecessary (but cool) double border effect in the chat area.

The two most important areas are the textarea with the id of "sendie" and the chat-area div. JavaScript will be targeting these.

The JavaScript Half of the Engine

We're going to get a little Object Oriented with our JavaScript. We'll create a "Chat" function that is parent to a bunch of other functions for dealing with Chat-related stuff.

function Chat () {
    this.update = updateChat;
    this.send = sendChat;
    this.getState = getStateOfChat;
}

updateChat will ask the server if there are new lines in the text file. If there are, it will return them as JSON and then this function will append those new lines to the chat. sendChat will be called when a message is entered into the text area and return is pressed. The function will pass that data to the server to do what it will with it. getStateOfChat asks the server basically how many lines the current text file is, so it has something to compare against and know when lines are "new" or not. This information is returned as JSON as well. And those functions look like:

//gets the state of the chat
function getStateOfChat() {
	if(!instanse){
		instanse = true;
		$.ajax({
			type: "POST",
			url: "process.php",
			data: {'function': 'getState', 'file': file},
			dataType: "json",	
			success: function(data) {state = data.state;instanse = false;}
		});
	}	
}

//Updates the chat
function updateChat() {
	if(!instanse){
		instanse = true;
		$.ajax({
			type: "POST",
			url: "process.php",
			data: {'function': 'update','state': state,'file': file},
			dataType: "json",
			success: function(data) {
				if(data.text){
					for (var i = 0; i < data.text.length; i++) {
						$('#chat-area').append($("

						"+ data.text[i] +"

						"));
					}	
				}
				document.getElementById('chat-area').scrollTop = document.getElementById('chat-area').scrollHeight;
				instanse = false;
				state = data.state;
			}
		});
	}
	else {
		setTimeout(updateChat, 1500);
	}
}

//send the message
function sendChat(message, nickname) { 
	updateChat();
	$.ajax({
		type: "POST",
		url: "process.php",
		data: {'function': 'send','message': message,'nickname': nickname,'file': file},
		dataType: "json",
		success: function(data){
			updateChat();
		}
	});
}

All three of these functions make use of jQuery's AJAX abilities, and do their communicating with a PHP file called process.php, which of course we'll need to build!

The PHP Half of the Engine

Part of the data that gets passed with the AJAX calls is an (arbitrary) value named "function". This is just to let the PHP file know what kind of thing we need done. As such, the first thing we'll do is snag that value and set up a switch statement that covers each possible function. We also set up a blank array to store values in that will ultimately be encoded into JSON at the end and passed back.

When we are trying to getState, the text file is read and the number of lines in returned. When we update, the file is read and any new lines are retuned. When we send, the message is processed and then written into the text file as a new line.

<?php

    $function = $_POST['function'];
    
    $log = array();
    
    switch($function) {
    
       case('getState'):
           if (file_exists('chat.txt')) {
               $lines = file('chat.txt');
           }
           $log['state'] = count($lines); 
           break;  
      
       case('update'):
          $state = $_POST['state'];
          if (file_exists('chat.txt')) {
             $lines = file('chat.txt');
          }
          $count =  count($lines);
          if ($state == $count){
             $log['state'] = $state;
             $log['text'] = false;
          } else {
             $text= array();
             $log['state'] = $state + count($lines) - $state;
             foreach ($lines as $line_num => $line) {
                 if ($line_num >= $state){
                       $text[] =  $line = str_replace("\n", "", $line);
                 }
             }
             $log['text'] = $text; 
          }
            
          break;
       
       case('send'):
       	 $nickname = htmlentities(strip_tags($_POST['nickname']));
	     $reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";
	     $message = htmlentities(strip_tags($_POST['message']));
	     if (($message) != "\n") {
	       if (preg_match($reg_exUrl, $message, $url)) {
	          $message = preg_replace($reg_exUrl, '<a href="'.$url[0].'" target="_blank">'.$url[0].'</a>', $message);
	       } 
	          fwrite(fopen('chat.txt', 'a'), "<span>". $nickname . "</span>" . $message = str_replace("\n", " ", $message) . "\n"); 
	     }
         break;
    }
    echo json_encode($log);
?>

Kicking it all off

We're going to need to do some JavaScript stuff to get this party started. We'll need to load jQuery, load the "engine", then do some quick functions to gather the chat participants name for joining the chat.

While we are at it, let's put the stuff in for limiting the length of the entered text and sending the text with a return-key press.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script src="chat.js"></script>
<script>

  // ask user for name with popup prompt    
  var name = prompt("Enter your chat name:", "Guest");
 
  // default name is 'Guest'
  if (!name || name === ' ') {
    name = "Guest";  
  }
  
  // strip tags
  name = name.replace(/(<([^>]+)>)/ig,"");
  
  // display name on page
  $("#name-area").html("You are: <span>" + name + "</span>");
  
  // kick off chat
  var chat =  new Chat();

  $(function() {
  
     chat.getState(); 
     
     // watch textarea for key presses
     $("#sendie").keydown(function(event) {  
     
         var key = event.which;  
   
         //all keys including return.  
         if (key >= 33) {
           
             var maxLength = $(this).attr("maxlength");  
             var length = this.value.length;  
             
             // don't allow new content if length is maxed out
             if (length >= maxLength) {  
                 event.preventDefault();  
             }  
         }  
                                                                                                     });
     // watch textarea for release of key press
     $('#sendie').keyup(function(e) {  
                
        if (e.keyCode == 13) { 
        
              var text = $(this).val();
              var maxLength = $(this).attr("maxlength");  
              var length = text.length; 
               
              // send 
              if (length <= maxLength + 1) { 
                chat.send(text, name);  
                $(this).val("");
              } else {
                $(this).val(text.substring(0, maxLength));
              }  
        }
     });
  });
</script>

Periodically Checking for New Messages

We need to use the "update" function of our chat to poll the text file for new messages and append them if necessary. So we'll need to call that update function on a regular basis, and we'll use JavaScript's setInterval() function for that:

<body onload="setInterval('chat.update()', 1000)">

The Goods

Download Files

Note: Remember this is PHP powered, so you can't just download the files and open them on your local machine and have it work, unless you are running a local PHP server. Also remember to change the file permissions of the chat.txt file to be writable by the server when uploading to your own test location.