AJAX Chat Implementation: Part 2

Exclusive offer: get 50% off this eBook here
AJAX and PHP: Building Modern Web Applications 2nd Edition

AJAX and PHP: Building Modern Web Applications 2nd Edition — Save 50%

Build user friendly Web 2.0 Applications with JavaScript and PHP

$23.99    $12.00
by Bogdan Brinzarea Cristian Darie | December 2009 | AJAX

This is the second part of the article series on AJAX Chat. In this article by Bogdan Brinzarea and Cristian Darie, we will continue to discuss about AJAX Chat implementation in detail. Read AJAX Chat: Part 1 here.

  1. Create a new file named index.html, and add this code to it:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
    http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <title>AJAX Chat</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="chat.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="jQuery-1.3.2.js" ></script>
    <script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="chat.js" ></script>

    </head>
    <body>
    <noscript>
    Your browser does not support JavaScript!!
    </noscript>
    <table id="content">
    <tr>
    <td>
    <div id="scroll">
    </div>
    </td>
    <td id="colorpicker">
    <img src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="palette.png" id="palette" alt="Color Palette" border="1"/>
    <br />
    <input id="color" type="hidden" readonly="true" value="#000000" />
    <span id="sampleText">
    (text will look like this)
    </span>
    </td>
    </tr>
    </table>
    <div>
    <input type="text" id="userName" maxlength="10" size="10"/>
    <input type="text" id="messageBox" maxlength="2000" size="50" />
    <input type="button" value="Send" id="send" />
    <input type="button" value="Delete All" id="delete" />
    </div>
    </body>
    </html>

  2. Let's deal with appearances now, creating chat.css and adding the following code to it:
    body 
    {
    font-family: Tahoma, Helvetica, sans-serif;
    margin: 1px;
    font-size: 12px;
    text-align: left
    }

    #content
    {
    border: DarkGreen 1px solid;
    margin-bottom: 10px
    }

    input
    {
    border: #999 1px solid;
    font-size: 10px
    }

    #scroll
    {
    position: relative;
    width: 340px;
    height: 270px;
    overflow: auto
    }

    .item
    {
    margin-bottom: 6px
    }

    #colorpicker
    {
    text-align:center
    }
  3. It's time for another test. We still don't have the color picker in place, but other than that, we have the whole client-server chat mechanism in place. Load index.html at http://localhost/ajax/chat/index.html from multiple browsers and/or computers, and ensure everything works as planned.
  4. Figure 8-7: Screenshot of index.html

  5. Copy palette.png from the code download to your ajax/chat folder.
  6. Create a file named color.php and add the following code to it. This is actually the only step left to make the color picker work as expected.
    <?php
    // the name of the image file
    $imgfile='palette.png';
    // load the image file
    $img=imagecreatefrompng($imgfile);
    // obtain the coordinates of the point clicked by the user
    $offsetx=$_GET['offsetx'];
    $offsety=$_GET['offsety'];
    // get the clicked color
    $rgb = ImageColorAt($img, $offsetx, $offsety);
    $r = ($rgb >> 16) & 0xFF;
    $g = ($rgb >> 8) & 0xFF;
    $b = $rgb & 0xFF;
    // return the color code
    echo json_encode(array("color" => sprintf('#%02s%02s%02s', dechex($r), dechex($g), dechex($b))));
    ?>
  7. Make another test to ensure the color picker works and that your users can finally chat in color.

What just happened?

First, make sure the application works well. Load http://localhost/ajax/chat/index.html with a web browser, and you should get a page that looks like the one in Figure 8-1.

If you analyze the code for a bit, the details will become clear. Everything starts with index.html. The only part that is really interesting in index.html is a scrolling region implemented in DHTML. (A little piece of information regarding scrolling can be found at http://www.dyn-web.com/code/scroll/.)

The scrolling area allows our users to scroll up and down the history of the chat and ensures that any new messages that might flow out of the area are still viewed. In our example, the scroll

element and its inner layers do the trick. The scroll element is the outermost layer; it has a fixed width and height; and its most useful property, overflow, determines how any content that falls (or overflows) outside of its boundaries is displayed. Generally, the content of a block box is confined to the content edges of the box. In CSS, the overflow property has four possible values that specify what should happen when an element overflows its area: visible, hidden, scroll, and auto. (For more details, please see the specification of overflow, at http://www.w3.org/TR/REC-CSS2/visufx.html.)

 

As you can see, the HTML file is very clean. It contains only the declarations of the HTML elements that make up the user interface. There are no event handlers and there is no JavaScript code inside the HTML file—we have a clean separation between the user interface and the programming.

In our client-side JavaScript code, in the chat.js file, the action starts with the ready event, which is defined in jQuery (reference: http://docs.jQuery.com/Events/ready) as a replacement for window.onload. In other words, your ready() function, which you can see in the following code snippet, is called automatically after the HTML page has been loaded by the browser, and the page elements can be safely used and manipulated by your JavaScript code:

$(document).ready(function() {
}

Inside this function, we do several operations involving events related to the user interface. Let's analyze each step!

We want to be sure that a username always appears, that is, it should never be left empty. To do this, we can create a function that checks for this and bind it to the blur event of the textbox.

// function that ensures that the username is never empty and //if so a random name is generated
$('#userName').blur(

function(e) {
// ensures our user has a default random name when the form loads
if (this.value == "")
this.value = "Guest" + Math.floor(Math.random() * 1000);
}
);

If the username is empty, we simply generate a random username suffixing Guest with a randomly generated number.

When the page first loads, no username has been set and we trigger the blur event on userName.

// populate the username field with 
// the default value
$('#userName').triggerHandler('blur');

The success() function starts by checking if the JSON response contains an errno field, which would mean that an error has happened on the server side. If an error occurred the displayPHPError() function is called passing in the error in JSON format.

// function that displays a PHP error message
function displayPHPError(error)
{
displayError ("Error number :" + error.errno + "rn" +
"Text :"+ error.text + "rn" +
"Location :" + error.location + "rn" +
"Line :" + error.line + + "rn");
}

The displayPHPError() will retrieve the information from the error and call in turn the displayError() function. The displayError() function shows the error message or a generic alert depending on whether the debugging flag is set or not.

// function that displays an error message
function displayError(message) {
// display error message, with more technical details if debugMode is true
alert("Error accessing the server! " +
(debugMode ? message : ""));
}

Next, in our ready event, we set the default color for the sample text to black:

// set the default color to black
$('#sampleText').css('color', 'black');

Moving on, we hook on to the click event of the Send button. The following code is very simple, as the entire logic behind sending a message is encapsulated in sendMessage():

$('#send').click(
function(e) {
sendMessage();
}
);

Moreover, here we hook on to the click event of the Delete all button in a similar way as the Send button.


$('#delete').click(
function(e) {
deleteMessages();
}
);

For the messageBox textbox, where we input messages, we disable autocomplete and we capture the Enter key and invoke the logic for sending a message:


// set autocomplete off
$('#messageBox').attr('autocomplete', 'off');

// handle the enter key event
$('#messageBox').keydown(
function(e) {
if (e.keyCode == 13) {
sendMessage();
}
}
);

Finally, when the page loads, we want to have the messages that have already been posted and we call retrieveNewMessages() function.

Now that we have seen what happens when the page loads, it's time to analyze the logic behind sending and receiving new messages.

Because everything starts when the page loads and the existing messages are retrieved, we will start with retrieveNewMessages() function. The function simply makes an AJAX request to the server indicating the retrieval of the latest messages.

function retrieveNewMessages() {
$.ajax({
url: chatURL,
type: 'POST',
data: $.param({
mode: 'RetrieveNew',
id: lastMessageID
}),
dataType: 'json',
error: function(xhr, textStatus, errorThrown) {
displayError(textStatus);
},
success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
else
readMessages(data);
// restart sequence
setTimeout("retrieveNewMessages();", updateInterval);
}
});
}

The request contains as parameters the mode indicating the retrieval of new messages and the ID of the last retrieved message:

data: $.param({
mode: 'RetrieveNew',
id: lastMessageID
}),

On success, we simply read the retrieved messages and we schedule a new automatic retrieval after a specific period of time:

success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
else
readMessages(data);
// restart sequence
setTimeout("retrieveNewMessages();", updateInterval);
}

Reading messages is the most complicated function as it involves several steps. It starts by checking whether the database has been cleared of messages and, if so, it empties the list of messages and resets the ID of the last retrieved message.

function readMessages(data, textStatus) {
// retrieve the flag that says if the chat window has been cleared or not
clearChat = data.clear;
// if the flag is set to true, we need to clear the chat window
if (clearChat == 'true') {
// clear chat window and reset the id
$('#scroll')[0].innerHTML = "";
lastMessageID = -1;
}

Before retrieving the new messages, we need to check and see if the received messages have not been already processed. If not, we simply store the ID of the last received message in order to know what messages to ask for during the next requests:

if (data.messages.length > 0)
{
// check to see if the first message
// has been already received and if so
// ignore the rest of the messages
if(lastMessageID > data.messages[0].id)
return;
// the ID of the last received message is stored locally
lastMessageID = data.messages[data.messages.length - 1].id;
}
AJAX and PHP: Building Modern Web Applications 2nd Edition Build user friendly Web 2.0 Applications with JavaScript and PHP
Published: December 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

If we have new messages from the server, we loop through the list of messages and perform the following tasks:

  1. We build the HTML for the message.
  2. We append the HTML of the new message to the current HTML list of messages.
  3. We check whether the scroll bar is positioned to the bottom and, if so, we update it:
    // display the messages retrieved from server
    $.each(data.messages, function(i, message) {
    // compose the HTML code that displays the message
    var htmlMessage = "";
    htmlMessage += "<div class="item" style="color:" + message.color + "">";
    htmlMessage += "[" + message.time + "] " + message.name + " said: <br/>";
    htmlMessage += message.message;
    htmlMessage += "</div>";

    // check if the scroll is down
    var isScrolledDown = ($('#scroll')[0].scrollHeight - $('#scroll')[0].scrollTop <=
    $('#scroll')[0].offsetHeight);

    // display the message
    $('#scroll')[0].innerHTML += htmlMessage;

    // scroll down the scrollbar
    $('#scroll')[0].scrollTop = isScrolledDown ? $('#scroll')[0].scrollHeight :
    $('#scroll')[0].scrollTop;
    }
    );

The rest of the function follows almost the same pattern as the retrieveNewMessages() function.

The sendMessage() function starts by retrieving the current chosen username, color, and message. If they are not empty, an AJAX request is made for saving this new message. We also use this request for retrieving new messages.

function sendMessage() {
var message = $.trim($('#messageBox').val());
var color = $.trim($('#color').val());
var username = $.trim($('#userName').val());

// if we need to send and retrieve messages
if (message != '' && color != '' & username != '') {
var params = {
mode: 'SendAndRetrieveNew',
id: lastMessageID,
color: color,
name: username,
message: message
};
$.ajax({
url: chatURL,
type: 'POST',
data: $.param(params),
dataType: 'json',
error: function(xhr, textStatus, errorThrown) {
displayError(textStatus);
},
success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
else
readMessages(data);
// restart sequence
setTimeout("retrieveNewMessages();", updateInterval);
}
});

}
}

The deleteMessages() function is the simplest function as it simply involves asking the server to clear all the messages. As with the other request, if new messages are posted after the messages are deleted, we also retrieve them.

function deleteMessages() {
$.ajax({
url: chatURL,
type: 'POST',
success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
else
readMessages(data);
// restart sequence
setTimeout("retrieveNewMessages();", updateInterval);
},
data: $.param({
mode: 'DeleteAndRetrieveNew'
}),
dataType: 'json',
error: function(xhr, textStatus, errorThrown) {
displayError(textStatus);
}
});
}

Let's move on to the server side of the application by first presenting the chat.php file. The server deals with clients' requests like this:

  • Retrieves the client's parameters
  • Identifies the operations that need to be performed/li>
  • Performs the necessary operations
  • Sends the results back to the client

The request includes the mode parameter, which specifies one of the following operations to be performed by the server:

  1. SendAndRetrieveNew: First the new messages are inserted in the database and then all new messages are retrieved and sent back to the client.
  2. DeleteAndRetrieveNew: All messages are erased and the new messages that might exist are fetched and sent back to the client.
  3. RetrieveNew: The new messages are fetched and sent back to the client.

The business logic behind chat.php lies in the chat.class.php script, which contains the Chat class.

The deleteMessages() method truncates the data table erasing all the information.

The postMessage() method inserts the new message into the database.

The isDatabaseCleared() method checks to see if all messages have been erased. Basically, by providing the ID of the last message retrieved from the server and by checking if it still exists, we can detect if all messages have been erased.

The retrieveNewMessages() method gets all new messages since the last message (identified by its id) retrieved from the server during the last request (if a last request exists; or all messages in other cases) and also checks to see if the database has been emptied by calling the isDatabaseCleared() method. This function composes the response for the client and sends it.

The config.php file contains the database configuration parameters and the error_handler.php file contains the module for handling errors.

How does the color picker work?

Here we use AJAX to call the PHP script that can tell us which text color was chosen by the user from the color palette. We use an image containing the entire spectrum of colors and allow the user choose any color for the text he or she writes. When the user clicks on the palette, the mouse coordinates are sent to the server, which obtains the color code, stores it in the user's DB entry, and sets the user's test to that color.

This part, which might seem pretty difficult, actually proves to be easy to implement. The relative position of the pixel in the palette is retrieved in the JavaScript code:

// handle the click event on the image
$('#palette').click(
function(e) {
// http://docs.jQuery.com/Tutorials:Mouse_Position
// retrieve the relative mouse position inside the image
var x = e.pageX - $('#palette').position().left;
var y = e.pageY - $('#palette').position().top;
);
}
);

Inside the same handler, we make an AJAX request and retrieve the corresponding RGB color using the color.php server-side page that contains the necessary functionality:

// make the ajax request to get the RGB code
$.ajax({
url: 'color.php',
success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
else
{
$('#color')[0].value = data.color;
$('#sampleText').css('color', data.color);
}
},
data: $.param({
offsetx: x,
offsety: y
}),
dataType: 'json',
error: function(xhr, textStatus, errorThrown) {
displayError(textStatus);
}
}

We have a palette image that contains the entire spectrum of visible colors. PHP has two functions that will help us in finding the RGB code of the chosen color: imagecreatefrompng() and imagecolorat(). These two functions allow us to obtain the RGB code of a pixel given the x and y position in the image.

$img=imagecreatefrompng($imgfile);
// obtain the coordinates of the point clicked by the user
$offsetx=$_GET['offsetx'];
$offsety=$_GET['offsety'];
// get the clicked color
$rgb = ImageColorAt($img, $offsetx, $offsety);

We mentioned above two PHP functions that we used to retrieve the RGB code of a pixel in an image. Let's see how they work:

  • imagecreatefrompng (string filename) returns an image identifier representing the image in PNG format obtained from the given filename.
  • int imagecolorat (resource image, int x, int y) returns the index of the color of the pixel at the specified location in the image specified by image. If PHP is compiled against GD library 2.0 or higher and the image is a true-color image, this function returns the RGB value of that pixel as an integer.

The first 8 bits of the result contain the blue code, the next 8 bits the green code, and the next 8 bits the red code. By using bit shifting and masking, we obtain the distinct red, green, and blue components as integer values. All that's left for us to do is to convert them to their hexadecimal value, to concatenate these values, and to send them to the client.

$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
// return the color code
echo json_encode(array("color" => sprintf('#%02s%02s%02s', dechex($r), dechex($g), dechex($b))));

When the data is returned from the server, the success callback function defined in the original AJAX request is called:

success: function(data, textStatus) {
if(data.errno != null)
displayPHPError(data);
$('#color')[0].value = data.color;
$('#sampleText').css('color', data.color);
},

The code is very simple and it simply involves setting up the sample text's color and storing the RGB color inside a hidden field. The sample text is useful for the user and the hidden field will be used for storing the message and its color, as we will see later.

Summary

We have learnt a lot about AJAX Chat.Step by step, we have implemented our AJAX chat solution, keeping it simple, easily extensible, and modular.

After reading this article, you can try improving the solution, by adding new features such as:

  • Chat rooms
  • Simple command lines (joining/leaving a chat room, switching between chat rooms)
  • Private messaging
AJAX and PHP: Building Modern Web Applications 2nd Edition Build user friendly Web 2.0 Applications with JavaScript and PHP
Published: December 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Bogdan Brinzarea

Bogdan has a strong background in Computer Science holding a Master and Bachelor Degree at the Automatic Control and Computers Faculty of the Politehnica University of Bucharest, Romania and also an Auditor diploma at the Computer Science department at Ecole Polytechnique, Paris, France.

His main interests cover a wide area from embedded programming, distributed and mobile computing and new web technologies. Currently, he is employed as an Alternative Channels Specialist at Banca Romaneasca, Member of National Bank of Greece where he is responsible for the Internet Banking project and coordinates other projects related to security applications and new technologies to be implemented in the banking area.

Cristian Darie

Cristian Darie is a software engineer with experience in a wide range of modern technologies, and the author of numerous books, including his popular AJAX and PHP tutorial by Packt, his ASP.NET E-Commerce tutorial, by APress and his forthcoming SEO tutorial for PHP developers by Wrox Press. Cristian is studying distributed application architectures for his PhD, and is getting involved with various commercial and research projects. When not planning to buy Google, he enjoys his bit of social life. If you want to say "hi", you can reach Cristian through his personal website at http://www.cristiandarie.ro.

Books From Packt

Apache MyFaces 1.2 Web Application Development
Apache MyFaces 1.2 Web Application Development

WordPress Plugin Development: Beginner's Guide
WordPress Plugin Development: Beginner's Guide

Apache MyFaces Trinidad 1.2: A Practical Guide
Apache MyFaces Trinidad 1.2: A Practical Guide

jQuery UI 1.7: The User Interface Library for jQuery
jQuery UI 1.7: The User Interface Library for jQuery

JBoss RichFaces 3.3
JBoss RichFaces 3.3

jQuery 1.3 with PHP
jQuery 1.3 with PHP

Joomla! with Flash
Joomla! with Flash

jQuery 1.3 with PHP
jQuery 1.3 with PHP

Your rating: None Average: 3 (1 vote)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Z
T
H
Q
F
7
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software