|
Title | AJAX and PHP: Building Responsive Web Applications | Author(s) | Cristian Darie, Bogdan Brinzarea, Filip Cherecheş-Toşa, Mihai Bucica | Publisher | Packt Publishing Ltd. | Published | March 2006 | ISBN | 1904811825 | Price | USD 31.49 | Pages | 275 |
|
Introduction
Online chat solutions have been very popular long before AJAX was born. There are numerous reasons for this popularity, and you're probably familiar with them if you've ever used an Internet Relay Chat (IRC) client, or an Instant Messenger (IM) program, or a Java chat applet.
AJAX has pushed online chat solutions forward by making it easy to implement features that are causing trouble or are harder to implement with other technologies. First of all, an AJAX chat application inherits all the typical AJAX benefits, such as integration with existing browser features, and (if written well) cross-platform compatibility.
An additional advantage is that an AJAX chat application avoids the connectivity problems that are common with other technologies, because many firewalls block the communication ports they use. On the other hand, AJAX uses, exclusively, HTTP for communicating with the server.
Probably the most impressive AJAX chat application available today is Meebo. We are pretty sure that some of you have heard about it, and if you haven't, we recommend you have a look at it. The first and the most important feature in Meebo is that it allows you to log in into your favorite IM system by using only a web interface. At the time of writing, Meebo lets you connect to AIM or ICQ, Yahoo! Messenger, Jabber, or GTalk, and MSN. You can access all these services from a single web page with a user friendly interface, with no pop-up windows or Java applets.
Meebo isn't the only web application that offers chat functionality. Even if AJAX is very young, a quick Google search on "AJAX Chat" will reveal several other applications.
It's time to get to work. In the rest of the chapter, we'll implement our own online chat application. We'll use this occasion to learn about JSON (JavaScript Object Notation), which represents an alternative to XML for representing data exchanged between a web browser and the web server.
Introducing JSON
JSON is a data format that you can use instead of XML for exchanging information between a JavaScript client and a PHP server script. Interestingly enough, JSON's popularity increased together with the AJAX phenomenon, although the AJAX acronym implies using XML.
Because XML is a more popular and more widely supported format, we've chosen to use XML for all examples in this book, except this one. However, if you like, it's fairly easy to update the other code samples to use JSON instead of XML. (As you know, this AJAX Chat chapter also has a version that uses XML, and you can compare them to see the differences.)
Perhaps the best short description of JSON is the one proposed by its official website: "JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate".
If you're new to JSON, a fair question you could ask would be: why another data exchange format? JSON, just as XML, is a text-based format that is easy to write and to understand for both humans and computers. The key word in the definition above is "lightweight". JSON data structures occupy less bandwidth than their XML versions.
To make an idea of how JSON compares to XML, let's take the same data structure and see how we would represent it using both standards. In the chat application you'll write, the server composes messages for the client that would look like this, if written in XML:
="1.0"="UTF-8"="yes"
<response>
<clear>false</clear>
<messages>
<message>
<id>1</id>
<color>#000000</color>
<time>2006-01-17 09:07:31</time>
<name>Guest550</name>
<text>Hello there! What's up?</text>
</message>
<message>
<id>2</id>
<color>#000000</color>
<time>2006-01-17 09:21:34</time>
<name>Guest499</name>
<text>This is a test message</text>
</message>
</messages>
</response>
The same message, written in JSON, this time, looks like this:
[
{"clear":"false"},
"messages":
[
{"message":
{"id":"1",
"color":"#000000",
"time":"2006-01-17 09:07:31",
"name":"Guest550",
"text":"Hello there! What's up?"}
},
{"message":
{"id":"2",
"color":"#000000",
"time":"2006-01-17 09:21:34",
"name":"Guest499",
"text":"This is a test message"}
}
]
}
]
As you can see, they aren't that different. If we disregard the extra formatting spaces that we added for better readability, the XML message occupies 396 bytes while the JSON message has only 274 bytes.
JSON is said to be a subset of JavaScript because it contains two basic structures: the object, and the array. An object is an unordered collection of name/value pairs, defined in this form:
{ name1:value1, name2:value2, ... }
The array is an ordered list of values defined in this form:
[ value1, value2, ... ]
The type of the value can be an object, a string, a number, an array, true, false, or null. A string is a collection of Unicode characters surrounded by double quotes. For escaping, we use the backslash.
For more detailed information, please refer to www.json.org, which covers the theory wonderfully.
Installing JSON
It's obvious that when you plan to use JSON, you need to be able to parse and generate JSON structures in JavaScript and PHP. JSON libraries are available for most of today's programming languages: ActionScript, C, C++, C#, Delphi, E, Erlang, Java, JavaScript, Lisp, Lua, ML and Ruby, Objective CAML, OpenLazslo, Perl, PHP, Python, Rebol, Ruby, and Squeak.
For JavaScript, we'll use the library listed here. The direct link to the small JSON library is this. The entire installation process consists in copying this file to your application's folder, and referencing it from the files that need its functionality.
The JSON solution we're using for PHP is the library developed by Michal Migurski that can be downloaded from here. Installing the library implies simply downloading the PHP class file, and copying it into the application's folder. Next, we need to reference it from our application by using the require_once
directive like this:
require_once('JSON.php');
In order to effectively start using it, we need to instantiate the class:
$json= new Services_JSON();
Then you're ready to use it. The encode and decode methods allow us to encode a PHP object into JSON format and to decode a JSON string into a PHP object.
Using JSON with JavaScript and PHP
A typical way to build a JSON structure in JavaScript:
var myMessages =
{'messages' : [
{'username': 'Guest0740',
'message': 'This is a JSON message',
'color': '#eeeeee'},
{'username': 'Guest0740',
'message': 'This is another JSON message',
'color': '#eeeeee'} ]};
alert(myMessages.toJSONString());
The first command defines an object called myMessages
, which contains a single member called messages
, which at its turn contains an array containing two objects that are made of three members (username, message, and color). In order to access the "This is another JSON message" text, you could use the following syntax:
myMessages[0].messages[1].message
The following two code snippets show typical ways to read a JSON structure in JavaScript:
myMessagesJSON = myMessages.toJSONString();
myMessagesFromJSON = myMessagesJSON.parseJSON();
Or
myMessagesFromJSON = eval ('(' + myMessagesJSON + ') ');
The eval
function is very fast but it can allow any JavaScript code to be executed, so it's generally much safer to use the parseJSON
method.
In PHP, you encode and decode JSON messages using the encode
and decode
methods. Let's take the previous example and do something similar in PHP. A typical way to build a JSON structure in PHP:
require_once('JSON.php');
$json = new Services_JSON();
$myMessages = array ('messages' =>
array(
array ('username' => 'Guest0740',
'message' => 'This is a JSON message',
'color' => '#eeeeee'),
array('username' => 'Guest0740',
'message' => 'This is another JSON message',
'color' => '#eeeeee')));
echo $json->encode($myMessages);
A typical way to read a JSON structure in PHP is:
require_once('JSON.php');
$json = new Services_JSON();
$myMessagesFromJSON = $json->decode($myMessagesJSON);
Implementing AJAX Chat
Now, it's time to implement the AJAX chat application. We'll keep the application simple, modular, and extensible. We won't implement a login module, support for chat rooms, the online users list, etc. By keeping it simple, we try to focus on what the goal of this chapter is: posting and retrieving messages without causing any page reloads. We'll also let the user pick a color for her or his messages, because this involves an AJAX mechanism that will be another good exercise.
The chat application can be tested online, and it looks like in Figure 5.1.
Figure 5.1: AJAX Chat
A novelty in this chapter is that you will have two XMLHttpRequest
objects. The first one will handle updating the chat window, and the second will handle the color picker (when you click on the image, the coordinates are sent to the server, and the server replies with the color code).
The messages for the AJAX chat are saved in a queue (a FIFO structure), whose functionality was covered in Chapter 4, so that messages are not lost even if the server is slow, and they always get to the server in the same order as you sent them. Unlike with other patterns you can find on Internet these days, we also ensure we don't load the server with any more requests until the current one is finished.
In order to have this example working, you need the GD library. The installation instructions in Appendix A include support for the GD library.
Time for Action—AJAX Chat
- Connect to the AJAX database, and create a table named chat with the following code:
CREATE TABLE chat
(
chat_id int(11) NOT NULL auto_increment,
posted_on datetime NOT NULL,
user_name varchar(255) NOT NULL,
message text NOT NULL,
color char(7) default '#000000',
PRIMARY KEY (chat_id)
);
- In your ajax folder, create a new folder named chat.
- Copy the palette.png file from the code download to the chat folder.
- We will create the application starting with the server functionality. In the chat folder, create a file named config.php, and add the database configuration code to it (change these values to match your configuration):
<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'ajaxuser');
define('DB_PASSWORD', 'practical');
define('DB_DATABASE', 'ajax');
?>
- Now, add the standard error handling file, error_handler.php:
<?php
set_error_handler('error_handler', E_ALL);
function error_handler($errNo, $errStr, $errFile, $errLine)
{
if(ob_get_length()) ob_clean();
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
exit;
}
?>
- Create another file named chat.php and add this code to it:
<?php
require_once('chat.class.php');
require_once('JSON.php');
$json=new Services_JSON();
$mode = $_POST['mode'];
$id = 0;
$chat = new Chat();
if($mode == 'SendAndRetrieveNew')
{
$name = $_POST['name'];
$message = $_POST['message'];
$color = $_POST['color'];
$id = $_POST['id'];
if ($name != '' && $message != '' && $color != '')
{
$chat->postMessage($name, $message, $color);
}
}
elseif($mode == 'DeleteAndRetrieveNew')
{
$chat->deleteMessages();
}
elseif($mode == 'RetrieveNew')
{
$id = $_POST['id'];
}
if(ob_get_length()) ob_clean();
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: text/javascript');
echo $json->encode($chat->retrieveNewMessages($id));
?>
- Create another file named chat.class.php, and add this code to it:
<?php
require_once('config.php');
require_once('error_handler.php');
class Chat
{
private $mMysqli;
function __construct()
{
$this->mMysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD,
DB_DATABASE);
}
public function __destruct()
{
$this->mMysqli->close();
}
public function deleteMessages()
{
$query = 'TRUNCATE TABLE chat';
$result = $this->mMysqli->query($query);
}
public function postMessage($name, $message, $color)
{
$name = $this->mMysqli->real_escape_string($name);
$message = $this->mMysqli->real_escape_string($message);
$color = $this->mMysqli->real_escape_string($color);
$query = 'INSERT INTO chat(posted_on, user_name, message, color) ' .
'VALUES (NOW(), "' . $name . '" , "' . $message .
'","' . $color . '")';
$result = $this->mMysqli->query($query);
}
public function retrieveNewMessages($id=0)
{
$id = $this->mMysqli->real_escape_string($id);
if($id>0)
{
$query =
'SELECT chat_id, user_name, message, color, ' .
' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") ' .
' AS posted_on ' .
' FROM chat WHERE chat_id > ' . $id .
' ORDER BY chat_id ASC';
}
else
{
$query =
' SELECT chat_id, user_name, message, color, posted_on FROM ' .
' (SELECT chat_id, user_name, message, color, ' .
' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") AS posted_on ' .
' FROM chat ' .
' ORDER BY chat_id DESC ' .
' LIMIT 50) AS Last50' .
' ORDER BY chat_id ASC';
}
$result = $this->mMysqli->query($query);
$response = array();
array_push( $response,array('clear'=> $this->isDatabaseCleared($id)));
$results=array();
if($result->num_rows)
{
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
$id = htmlspecialchars($row['chat_id']);
$color = htmlspecialchars($row['color']);
$userName = htmlspecialchars($row['user_name']);
$time = htmlspecialchars($row['posted_on']);
$message = htmlspecialchars($row['message']);
array_push($results,array('id' => $id ,
'color' => $color ,
'time' => $time ,
'name' => $userName ,
'message' => $message ));
}
$result->close();
}
array_push($response, array('results' => $results));
return $response;
}
private function isDatabaseCleared($id)
{
if($id>0)
{
$check_clear = 'SELECT count(*) old FROM chat where chat_id<=' . $id;
$result = $this->mMysqli->query($check_clear);
$row = $result->fetch_array(MYSQLI_ASSOC);
if($row['old']==0)
return 'true';
}
return 'false';
}
}
?>
- Create another file named get_color.php and add this code to it:
<?php
$imgfile='palette.png';
$img=imagecreatefrompng($imgfile);
$offsetx=$_GET['offsetx'];
$offsety=$_GET['offsety'];
$rgb = ImageColorAt($img, $offsetx, $offsety);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
printf('#%02s%02s%02s', dechex($r), dechex($g), dechex($b));
?>
- Let's deal with the client now. Start by creating chat.css and add this 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
}
- 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="json.js" ></script>
<script type="text/javascript" src="chat.js" ></script>
</head>
<body onload="init();">
<noscript>
Your browser does not support JavaScript!!
</noscript>
<table id="content">
<tr>
<td>
<div id="scroll">
</div>
</td>
<td id="colorpicker">
<img src="palette.png" id="palette" alt="Color
Palette" border="1" onclick="getColor(event);"/>
<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" onblur="checkUsername();"/>
<input type="text" id="messageBox" maxlength="2000" size="50"
onkeydown="handleKey(event)"/>
<input type="button" value="Send" onclick="sendMessage();" />
<input type="button" value="Delete All" onclick="deleteMessages();" />
</div>
</body>
</html>
- Create another file named chat.js and add this code to it:
var chatURL = "chat.php";
var getColorURL = "get_color.php";
var xmlHttpGetMessages = createXmlHttpRequestObject();
var xmlHttpGetColor = createXmlHttpRequestObject();
var updateInterval = 1000;
var debugMode = true;
var cache = new Array();
var lastMessageID = -1;
var mouseX,mouseY;
function createXmlHttpRequestObject()
{
var xmlHttp;
try
{
xmlHttp = new XMLHttpRequest();
}
catch(e)
{
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
"MSXML2.XMLHTTP.5.0",
"MSXML2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0",
"MSXML2.XMLHTTP",
"Microsoft.XMLHTTP");
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}
function init()
{
var oMessageBox = document.getElementById("messageBox");
oMessageBox.setAttribute("autocomplete", "off");
var oSampleText = document.getElementById("sampleText");
oSampleText.style.color = "black";
checkUsername();
requestNewMessages();
}
function checkUsername()
{
var oUser=document.getElementById("userName");
if(oUser.value == "")
oUser.value = "Guest" + Math.floor(Math.random() * 1000);
}
function sendMessage()
{
var oCurrentMessage = document.getElementById("messageBox");
var currentUser = document.getElementById("userName").value;
var currentColor = document.getElementById("color").value;
if (trim(oCurrentMessage.value) != "" &&
trim(currentUser) != "" && trim (currentColor) != "")
{
params = "mode=SendAndRetrieveNew" +
"&id=" + encodeURIComponent(lastMessageID) +
"&color=" + encodeURIComponent(currentColor) +
"&name=" + encodeURIComponent(currentUser) +
"&message=" + encodeURIComponent(oCurrentMessage.value);
cache.push(params);
oCurrentMessage.value = "";
}
}
function deleteMessages()
{
params = "mode=DeleteAndRetrieveNew";
cache.push(params);
}
function requestNewMessages()
{
var currentUser = document.getElementById("userName").value;
var currentColor = document.getElementById("color").value;
if(xmlHttpGetMessages)
{
try
{
if (xmlHttpGetMessages.readyState == 4 ||
xmlHttpGetMessages.readyState == 0)
{
var params = "";
if (cache.length>0)
params = cache.shift();
else
params = "mode=RetrieveNew" +
"&id=" +lastMessageID;
xmlHttpGetMessages.open("POST", chatURL, true);
xmlHttpGetMessages.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
xmlHttpGetMessages.onreadystatechange = handleReceivingMessages;
xmlHttpGetMessages.send(params);
}
else
{
setTimeout("requestNewMessages();", updateInterval);
}
}
catch(e)
{
displayError(e.toString());
}
}
}
function handleReceivingMessages()
{
if (xmlHttpGetMessages.readyState == 4)
{
if (xmlHttpGetMessages.status == 200)
{
try
{
readMessages();
}
catch(e)
{
displayError(e.toString());
}
}
else
{
displayError(xmlHttpGetMessages.statusText);
}
}
}
function readMessages()
{
var response = xmlHttpGetMessages.responseText;
if (response.indexOf("ERRNO") >= 0
|| response.indexOf("error:") >= 0
|| response.length == 0)
throw(response.length == 0 ? "Void server response." : response);
responseJSON = xmlHttpGetMessages.responseText.parseJSON();
clearChat = responseJSON[0].clear;
if(clearChat == "true")
{
document.getElementById("scroll").innerHTML = "";
lastMessageID = -1;
}
idArray = new Array();
colorArray = new Array();
nameArray = new Array();
timeArray = new Array();
messageArray = new Array();
for(i=0;i<responseJSON[1].results.length;i++)
{
idArray[i] = responseJSON[1].results[i].id;
colorArray[i] = responseJSON[1].results[i].color;
nameArray[i] = responseJSON[1].results[i].name;
timeArray[i] = responseJSON[1].results[i].time;
messageArray[i] = responseJSON[1].results[i].message;
}
displayMessages(idArray, colorArray,
nameArray, timeArray, messageArray);
if(idArray.length>0)
lastMessageID = idArray[idArray.length - 1];
setTimeout("requestNewMessages();", updateInterval);
}
function displayMessages(idArray, colorArray, nameArray,
timeArray, messageArray)
{
for(var i=0; i<idArray.length; i++)
{
var color = colorArray[i];
var time = timeArray[i];
var name = nameArray[i];
var message = messageArray[i];
var htmlMessage = "";
htmlMessage += "<div class=\"item\" style=\"color:" + color + "\">";
htmlMessage += "[" + time + "] " + name + " said: <br/>";
htmlMessage += message.toString();
htmlMessage += "</div>";
displayMessage (htmlMessage);
}
}
function displayMessage(message)
{
var oScroll = document.getElementById("scroll");
var scrollDown = (oScroll.scrollHeight - oScroll.scrollTop <=
oScroll.offsetHeight );
oScroll.innerHTML += message;
oScroll.scrollTop = scrollDown ? oScroll.scrollHeight : oScroll.scrollTop;
}
function displayError(message)
{
displayMessage("Error accessing the server! "+
(debugMode ? "<br/>" + message : ""));
}
function handleKey(e)
{
e = (!e) ? window.event : e;
code = (e.charCode) ? e.charCode :
((e.keyCode) ? e.keyCode :
((e.which) ? e.which : 0));
if (e.type == "keydown")
{
if(code == 13)
{
sendMessage();
}
}
}
function trim(s)
{
return s.replace(/(^\s+)|(\s+$)/g, "")
}
function getMouseXY(e)
{
if(window.ActiveXObject)
{
mouseX = window.event.x + document.body.scrollLeft;
mouseY = window.event.y + document.body.scrollTop;
}
else
{
mouseX = e.pageX;
mouseY = e.pageY;
}
}
function getColor(e)
{
getMouseXY(e);
if(xmlHttpGetColor)
{
var offsetX = mouseX;
var offsetY = mouseY;
var oPalette = document.getElementById("palette");
var oTd = document.getElementById("colorpicker");
if(window.ActiveXObject)
{
offsetX = window.event.offsetX;
offsetY = window.event.offsetY;
}
else
{
offsetX -= oPalette.offsetLeft + oTd.offsetLeft;
offsetY -= oPalette.offsetTop + oTd.offsetTop;
}
try
{
if (xmlHttpGetColor.readyState == 4 ||
xmlHttpGetColor.readyState == 0)
{
params = "?offsetx=" + offsetX + "&offsety=" + offsetY;
xmlHttpGetColor.open("GET", getColorURL+params, true);
xmlHttpGetColor.onreadystatechange = handleGettingColor;
xmlHttpGetColor.send(null);
}
}
catch(e)
{
displayError(xmlHttp.statusText);
}
}
}
function handleGettingColor()
{
if (xmlHttpGetColor.readyState == 4)
{
if (xmlHttpGetColor.status == 200)
{
try
{
changeColor();
}
catch(e)
{
displayError(xmlHttpGetColor.statusText);
}
}
else
{
displayError(xmlHttpGetColor.statusText);
}
}
}
function changeColor()
{
response=xmlHttpGetColor.responseText;
if (response.indexOf("ERRNO") >= 0
|| response.indexOf("error:") >= 0
|| response.length == 0)
throw(response.length == 0 ? "Can't change color!" : response);
var oColor = document.getElementById("color");
var oSampleText = document.getElementById("sampleText");
oColor.value = response;
oSampleText.style.color = response;
}
- If you haven't already done so, copy json.js to your project's folder.
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 5.1.
Technically, the application is split in two smaller applications that build the final solution:
- The chat application: Here, we use AJAX for passing the messages between the client and the server. The chat window contacts the server periodically to send any messages that have been typed by the user, and retrieve the newly posted messages from the server.
- The code for choosing a color: Here, we use AJAX for calling the PHP script that can tell us the color that was clicked by the user on the color image. We use a palette containing the entire spectrum of colors to allow the user to pick a color for the text he or she writes. When clicking on the palette, the mouse coordinates are sent to the server, which obtains the color code.
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 scroll region that can be implemented in DHTML. A little piece of information regarding scrolling can be found here.
Basically, the idea for having a part of the page with a scrollbar next to it is to use nested layers. In our example, the div
scroll and its inner layers do the trick. The outer layer is scroll. It has a fixed width and height, and the most useful property of it is overflow. Generally, the content of a block box is confined to the content edges of the box. In certain cases, a box may overflow, meaning its content lies partly or entirely outside of the box. In CSS, this property specifies what happens when an element overflows its area. For more details, please see the overflow's specification.
The chat.js script contains the JavaScript part for our application. This file can be divided in two parts: the one that handles choosing a color, and the other that handles retrieving and sending chat messages.
We will start with the color choosing functionality. This part, which in the beginning might seem pretty difficult, proves to be easy to implement. Let's have a panoramic view of the entire process.
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. The position of the pixel is retrieved in the getMouseXY
function in the JavaScript code.
The getColor
function retrieves the RGB code of the color chosen by the user when clicking the palette image. First of all, it retrieves the mouse coordinates from the event. Then, it computes the coordinates where the click event has been produced as relative values within the image. Using this information, getColor
initiates an asynchronous request to the get_color.php script, which is supposed to return the color code associated with the clicked coordinate. The callback function handleGettingColor
is executed when the state of the request is changed.
The handleGettingColor
function checks to see when the request to the server is completed and if no errors occurred, the changeColor
function is called. This function changes the color of the sample text "text will look like this" with the given code.
Now, let's now see how the chat works. By default, when the page initializes and the OnBlur
event occurs, the checkUsername
function is called. This function ensures that the name of the user isn't empty, by generating an arbitrary username.
On pressing the Send button, the sendMessage
function is called. This function adds the current message to the message queue to be sent to the server. Before adding it into the queue, the function trims the message by calling the trim
function, and we encode the message using encodeURIComponent
to make sure it gets through successfully.
The handleKey
function is called whenever a keydown
event occurs. When the Enter key is pressed, the sendMessage
function is called so that both pressing the Send button and pressing Enter within the MessageBox
control have the same effect.
The deleteMessages
function adds the delete message to the messages to be sent to the server.
The requestNewMessages
function is responsible for sending chat messages. It retrieves a message from the queue and sends it to the server. The change of state of the HTTP request object is handled by the handleReceivingMessages
function.
The handleReceivingMessages
checks to see when the request to the server is completed, and if no errors occurred, then the readMessages
function is called.
The readMessages
function checks to see if someone else erased all the chat messages, and if so, the client's chat window is also emptied. In order to append new messages to the chat, we call the displayMessages
function. This function takes as parameters the arrays that correspond to the new messages. It composes the new messages as HTML, and it appends them to those already in the chat, by calling the displayMessage
function. In the beginning, the displayMessage
function checks to see if the scroll bar is at the bottom of the list of messages. This is necessary in order to reposition it at the end of the function so that the focus is now on the last new messages.
The last function presented is the init
function. Its role is to retrieve the chat messages, to ensure that the username is not null, to set the text's color to black, and to turn off the auto complete functionality.
For the error handling part, we use the displayError
function, which calls the displayMessage
function in turn with the error message as parameter.
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.
- Performs the necessary operations.
- Sends the results back to the client.
The request includes the mode
parameter that specifies one of the following operations to be performed by the server:
SendAndRetrieve
: First, the new messages are inserted in the database, and then all new messages are retrieved and sent back to the client.
DeleteAndRetrieve
: All messages are erased, and the new messages that might exist are fetched and sent back to the client.
Retrieve
: 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 postMessages
method inserts all the new messages into the database.
The isDatabaseCleared
method checks to see if all the 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 the 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.
Summary
At the beginning of the chapter, we saw why one can face problems when communicating with other people in a dynamic way over the Internet. We saw what the solutions for these problems are and how AJAX chat solutions can bring something new, useful, and ergonomic. After seeing some other AJAX chat implementations, we started building our own solution. Step by step, we have implemented our AJAX chat solution, keeping it simple, easily extensible, and modular.
After reading this chapter, you can try improving the solution, by adding new features like:
- Chat rooms
- Simple command lines (joining/leaving a chat room, switching between chat room)
- Private messaging
AJAX and PHP: Building Responsive Web Applications
AJAX is a complex phenomenon that means different things to different people. Computer users appreciate that their favorite websites are now friendlier, and feel more responsive. Web developers learn new skills that empower them to create sleek web applications with little effort. Indeed, everything sounds good about AJAX!
At its roots, AJAX is a mix of technologies that lets you get rid of the evil page reload, which represents the dead time when navigating from one page to another. Eliminating page reloads is just one step away from enabling more complex features into websites, such as real-time data validation, drag and drop, and other tasks that weren't traditionally associated with web applications. Although the AJAX ingredients are mature (the XMLHttpRequest
object, which is the heart of AJAX, was created by Microsoft in 1999), their new role in the new wave of web trends is very young, and we'll witness a number of changes before these technologies will be properly used to the best benefit of the end users. At the time of writing this book, the "AJAX" name is about just one year old.
AJAX isn't, of course, the answer to all the Web's problems, as the current hype around it may suggest. As with any other technology, AJAX can be overused, or used the wrong way. AJAX also comes with problems of its own: you need to fight with browser inconsistencies, AJAX‑specific pages don't work on browsers without JavaScript, they can't be easily bookmarked by users, and search engines don't always know how to parse them. Also, not everyone likes AJAX. While some are developing enterprise architectures using JavaScript, others prefer not to use it at all. When the hype is over, most will probably agree that the middle way is the wisest way to go for most scenarios.
In AJAX and PHP: Building Responsive Web Applications, we took a pragmatic and safe approach by teaching relevant patterns and best practices that we think any web developer will need sooner or later. We teach you how to avoid the common pitfalls, how to write efficient AJAX code, and how to achieve functionality that is easy to integrate into current and future web applications, without requiring you to rebuild the whole solution around AJAX. You'll be able to use the knowledge you learn from this book right away, into your PHP web applications.