A JQuery Chat

Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

As much as I resented the fact that I had to learn yet another framework, there came a time when I couldn’t avoid JQuery any more. Then, in order to get a hands-on experience with it, I decided to build a simple web-based chat. The concept is fairly simple: The server keeps track of a chat room (ie the connected users, what everyone says and when they say it). On the other side, a client script allows a user to send messages and frequently polls the server for an updated state of the room.

While a user is defined as an entity with just a username field, a conversation is a set of tuples with the following fields:

  • message: what was said
  • username: who said it
  • msg_time: when it was said
  • mark: a sortable field (preferably an integer one) which defines the order of messages in the log. Messages with greater marks are logged after messages with smaller ones.

When the client polls for updates, all the above data is transfered in a convenient JSON-encoded object.

To start implementing, we will need an HTML frontend — let’s call it chat.html.

<!doctype html>
<html lang="en">
    <head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>Chat Demo</title>
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
		<script src="chat.js" type="text/javascript"></script>
		<link href="chat.css" type="text/css" rel="stylesheet"/> 
 	</head>
	<body>
		<div id='main'>
			<div id="chatbox"><div id="room"></div><div id="room_panel"></div></div>
			<div id="chat_message">
				<label>
					<em>Type your text and press enter:</em>
					<input id="usermsg" type="text" name="usermsg"/>
				</label>
			</div>
		</div>
	</body>
</html>	

Let’s examine the key elements:

  • The chatbox div will visualize the state of the chatroom. It is divided into two sections, room and room_panel. The former will contain the conversation while the latter will provide a list of connected users.
  • The usermsg input element will be used to send messages to the server. As its label suggests, when the enter key is pressed, the input element’s contents will be sent.

All communication with the server will be handled by a single php script, called chat.php. If the request is not a POST one, the frontend is served. For POST requests, the script examines whether a non-null msg field is being posted. If so, the message should be logged. Otherwise, if a mark field is being posted, the script returns a JSON-encoded array with two elements: the conversation and the users.

<?php
require "chat_aux.php";
$me=get_username();
if(count($_POST)==0) require "chat.html";
elseif(isset($_POST['msg'])&&($_POST['msg'])) say($me,$_POST['msg']);
elseif(isset($_POST['mark']))
    echo json_encode(
		array(
			'conversation'=>get_conversation($_POST['mark']),
			'users'=>get_active_users(),
			)
		);

In the above listing, there are four implementation-specific functions: get_username(), say(), get_conversation() and get_active_users(). These should be implemented inside the required chat_aux.php file, and will not be discussed here. However, this is what we expect them to do:

  • get_username(): returns the logged-in user’s username. It is responsible for checking if the connected user is really logged in and/or initializing a login sequence
  • say($user,$message): logs the $user‘s $message to the chat log
  • get_conversation($mark): returns an array containing the last messages logged, from $mark to the latest
  • get_active_users(): returns an array containing the users who are logged in

Note that, for the last two functions, the returned array’s elements could be either associative arrays or objects containing the fields described earlier.

Now that the necessary server-side functionality is covered, let’s deal with the main subject of this article: the client-side script. Everything will be coded inside a single javascript file, namely chat.js:

var updTimer, curMark=0, lock=0;

function roomScrollDown()
    {
	$("#room").scrollTop($("#room")[0].scrollHeight);
	}
	
function showUsers(u)
	{
	$('#room_panel').empty();
	for(i=0;i<u.length;i++) 
		$('#room_panel').append("<p><strong><em>"+u[i].username+"</em></strong></p>");
	}

function showConversation(conv)
	{
	if(conv.length)
		{
		scrolled=($('#room').scrollTop()+$('#room').innerHeight()==$('#room')[0].scrollHeight);
		for(i=0;i<conv.length;i++) 
			{
			el=conv[i];
			u="<span title='"+el.msg_time+"'><strong><em>"+el.username+":</em></strong></span>";
			$('#room').append("<p>"+u+" "+el.message+"</p>");
			}
		curMark=el.mark;
		if(scrolled) roomScrollDown();
		}
	}
	
function updateEnv(arr)
	{
	showConversation(arr['conversation']);
	showUsers(arr['users']);
	}
	
function setLock()
	{
	clearTimeout(updTimer);
	lock=1;
	}
	
function clearLock()
	{
	lock=0;
	updTimer=setTimeout(updateRoom,5000);
	}
	
function updateRoom()
	{
	if(lock) 
		setTimeout(updateRoom,1000);
	else
		{
		setLock();
		$.ajax({
			type: "POST",
			data: {mark: curMark},
			dataType: 'json',
			success: updateEnv,
			complete: clearLock
			});
		}
	}
	
function messageSent(msg)
	{
	if(!msg) 
		{
		roomScrollDown();
		updateRoom();
		} 
	else 
		alert(msg);
	}

function sendChatMessage(msg)
	{
	$.ajax({
		type: "POST",
		data: {msg: msg},
		dataType: 'text',
		success: messageSent
		});
	}

$(document).ready(function() 
	{ 
	$('#usermsg').keyup(function(e)
		{
		if(e.keyCode==13) 
			{
			sendChatMessage(usermsg.value);
			usermsg.value='';
			}
		});
	updateRoom(); 
	}); 
    

Upon page load, an event handler is hooked to the keyup event of the usermsg input element, and an initial state update is triggered by calling updateRoom(). The keyup event handler, provided that the enter key was pressed, calls the sendChatMessage() function before clearing the input element’s contents.

Let’s see how each function works:

sendChatMessage(msg): Dispatches the user-typed message to the server using an AJAX POST request with the msg parameter as a post field. If the request succeeds, messageSent() is called.

messageSent(msg): Checks if the server response to the above request was null. If not, a popup appears notifying the user. Otherwise, we provide instant feedback to the user by calling updateRoom(). But first, we must make sure the room div is scrolled all the way down.

updateRoom(): This function is intended to execute an AJAX POST request, expecting a JSON object in response, which represents the current state of the conversation (see above). If re request succeeds, updateEnv() is called with the JSON object as a parameter, to update the visual environment.

The asynchronous nature of the request, as well as the triggering of the function by different events, eventually leads to “parallel” execution of multiple requests. If a second request reads curMark before the first has the chance to update it, it will receive the wrong conversation section. Therefore, we must implement a locking mechanism that surrounds both the request and the calling of updateEnv(). This is accomplished by calling setLock() and clearLock() before and after the request respectively. This way, if the lock is asserted, we can schedule the function’s execution for a period of time ahead.

clearLock(): Sets the lock‘s value to zero, thus enabling another (pending or future) request to execute. Also, schedules the next update for a predefined interval.

setLock(): Sets the lock‘s value to 1, preventing other requests to execute before it is set back to zero, and clears any scheduled update.

updateEnv(): It is called when an update request is successful, with the returned JSON object as the arr parameter. In turn, it invokes showConversation() and showUsers() in order to update the room and room_panel divs respectively.

showConversation(conv): The conv parameter is the conversation part of the JSON object. Each of its elements is formatted and appended to the contents of the room div. If the latter was scrolled down, it will be scrolled again.

showUsers(): Similarily, the u parameter contains the users part of the object. This time, the room_panel div’s contents are replaced, because u contains a full list of connected users.

roomScrollDown(): Scrolls the room div to the bottom. This is needed in two occasions: (1) when a user posts a message and receives feedback and (2) when the room updates its state and was already scrolled down.

That’s about it. You can see the script in action below, in a demo implementation. And remember, comments are always welcome!

VN:F [1.9.22_1171]
Rating: 5.0/5 (1 vote cast)
A JQuery Chat, 5.0 out of 5 based on 1 rating
Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

2 comments on “A JQuery Chat

  1. Pingback: geomagas.gr » Blog Archive » A JQuery Chat: A Sample chat_aux.php

Submit comment

Allowed HTML tags: <a href="http://google.com">google</a> <strong>bold</strong> <em>emphasized</em> <code>code</code> <blockquote>
quote
</blockquote>