This tutorial will walk you through the steps
to create an JSON AJAX driven website.
We will be passing the message
data back as XML, we will be using JSON.
JSON AJAX Web Chat
This tutorial will walk you through the steps to create an JSON AJAX driven website.
So what is JSON?
JSON stands for JavaScript Object Notation and is basically a
lightweight way of describing hierarchical data. Since it is so
lightweight,
it makes it an ideal candidate for AJAX applications. So
what does JSON look like.
The JSON code our JSON AJAX Chat application
will be returning will look something like this:
{"messages":
{"message":[
{"id": "17",
"user": "Ryan Smith",
"text": "This is an example of JSON",
"time": "04:41"
},{"id": "18",
"user": "Ryan Smith",
"text": "Here is another Element",
"time": "04:41"
} ]
}
}
As you can tell, it looks a lot like structured data - and it is.
This same data structure might be represented with XML like:
<?xml version="1.0" ?>
>
id="17">
>Ryan Smith>
>This is an example of JSON>
>04:41>
>
id="18">
>Ryan Smith>
>Here is another Element>
>04:41>
>
>
There are some other cool things you can do with JSON link making
embedded JavaScript calls,
but they are beyond the scope of this
tutorial.
Creating the Chat Tables
So lets get on with it. The first thing that we need to do is to setup
our database table.
We really only need one table that holds the
messages, but I keep thinking one day I'll expand this
tutorial in to a
full AJAX chat system, so we'll add both tables for now.
--Chat Table
DROP TABLE IF EXISTS `chat`;
CREATE TABLE `chat` (
`chat_id` INT(11) NOT NULL AUTO_INCREMENT,
`chat_name` VARCHAR(64) DEFAULT NULL,
`start_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`chat_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
--Message Table
DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`message_id` INT(11) NOT NULL AUTO_INCREMENT,
`chat_id` INT(11) NOT NULL DEFAULT '0',
`user_id` INT(11) NOT NULL DEFAULT '0',
`user_name` VARCHAR(64) DEFAULT NULL,
`message` TEXT,
`post_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
The first table 'chat' won't be necessary for this tutorial. The
second table message will
hold our list of messages that are sent from
our JSON AJAX Chat web page.
It basically consists of who sent the
message, when they sent it,
and what the message was.
The field
chat_id would be used if you wanted to have more than one chat session.
The HTML Skeleton
Now that
we have created our database tables, we need to create our HTML
Skeleton.
This is a very basic layout that could easily be made to
look nicer with CSS,
but we're not concerned with looks right now. We
just want the basic functionality.
For the sake of the demo,
we'll just put the necessary CSS style information on the HTML page in
a style tag.
In a production environment,
you should place your CSS
into an external file for caching benefits, especially when you start
having lots of CSS rules.
We will also place our JavaScript on
the HTML page in a script tag for
the sake of the demo. We will write
that in just a few minutes.
Our basic HTML page will look like:
JSON AJAX Driven Web Chat
type="text/css" media="screen">
language="JavaScript" type="text/javascript">
AJAX Driven Web Chat.id="p_status">Status: Normal
Current Chitter-Chatter:
id="div_chat" class="chat_main">
id="frmmain" name="frmmain" onsubmit="">
type="button" name="btn_get_chat" id="btn_get_chat" value="Refresh Chat" />
type="button" name="btn_reset_chat" id="btn_reset_chat" value="Reset Chat" />
/>
type="text" id="txt_message" name="txt_message" style="width: 447px;" />
type="button" name="btn_send_chat" id="btn_send_chat" value="Send" />
As you can see we have a simple header that is just a title, a
paragraph where we can display status messages in the event of any
errors, and a main DIV for displaying the chit-chat.
We also have an HTML form with 4 HTML control elements. We have a
refresh button to restart the
JavaScript timer in the event of an
error.
This button should only be for test purposes. We have a button
to reset the chat which will clear all
the messages off the screen.
Finally, a text area and a button to send a new chat message to the
server.
In the HTML, we have one inline CSS class on the text
box to expand its width, and one CSS class on the main DIV that hasn't
been defined yet.
The undefined CSS class will look like:
overflow: auto;
height: 300px;
width: 500px;
background-color: #CCCCCC;
border: 1px solid #555555;
All these values should be pretty self explanatory with the exception
of "overflow".
The overflow: auto; attribute allow the DIV to behave
somewhat like an IFrame in the sense that when
the content is larger
than the DIV's size, scrollbars will
be provided rather than expanding
the dimensions.
I try to never use IFrames because search engines have
a hard time indexing them. Plus I
started web programming in a time
when not all browsers supported IFrames so you couldn't use them anyway.
The JavaScript
Now onto the AJAX! It's time to write all the magic code that makes
AJAX so neat. We'll start with my favorite piece of AJAX code.
//Gets the browser specific XmlHttpRequest Object
function getXmlHttpRequestObject() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
} else {
document.getElementById('p_status').innerHTML =
'Status: Cound not create XmlHttpRequest Object.' +
'Consider upgrading your browser.';
}
}
This simply returns a browser specific XmlHttpRequest object - the
basis for AJAX functionality.
The XmlHttpRequest object allows us to
make asynchronous requests to the web server without refreshing the
page.
IE uses an ActiveX object where Firefox and most of the other browsers
out there use a native object.
IE 7 allows the use of both a native
object and the ActiveX object which would eliminate the need for this
block of code,
however people will be using
IE 6 for many years to come
so you better get use to writing this.
We can now use this code to create a browser specific XmlHttpRequest object anywhere in our page.
Lets start out by adding four global variables to the top of our page.
var sendReq = getXmlHttpRequestObject();
var receiveReq = getXmlHttpRequestObject();
var lastMessage = 0;
var mTimer;
We need two XmlHttpRequest objects - one for sending new chat messages
and one for receiving chat messages.
We also need a variable to store
the last message we have received to avoid sending the entire message
list each time, and a timer to
periodically poll the server for new
messages. We place the auto refresh timer in a global
variable so that
we can clear out the setTimeout at any point to avoid having more than
one timer running at once.
Now let's add the function to make the call to receive the most recent messages on the server.
//Gets the current messages from the server
function getChatText() {
if (receiveReq.readyState == 4 || receiveReq.readyState == 0) {
receiveReq.open("GET", 'getChat.php?chat=1&last=' + lastMessage, true);
receiveReq.onreadystatechange = handleReceiveChat;
receiveReq.send(null);
}
}
The first line of code in the function checks to make sure our
XmlHttpRequest object is not
currently in the middle of a different
request. 0 is uninitiated and 4 is complete.
Any other readyState is
in the middle of a request.
The next line setups up the connection to the server:
receiveReq.open("GET", 'getChat.php?chat=1&last=' + lastMessage, true);
Since we aren't sending much data a standard HTTP "GET" will be fine.
When we send the message later you will see how to create an AJAX
request using a HTTP "POST"
The second parameter is the URL we want to make the AJAX request to.
Notice that we are passing a
parameter in the URL querystring that
contains the last message we received as well as the chat session that
we want messages for.
The chat parameter is hard coded as a 1, but if
we wanted to have more than one chat room,
we could make this a dynamic
parameter based on which chat session we were in.
The last
parameter, "true", is a flag used to mark if the request is
asynchronous or not.
We don't really need this parameter because by
default it is asynchronous, but will explicitly set it anyway.
The next line sets the call back function that will get executed every time the the XmlHttpRequest objects readyState changes.
receiveReq.onreadystatechange = handleReceiveChat;
We will simply set this to a function we will write in just a minute.
The third and file line makes the actual AJAX request to the server.
The null value parameter is where
you could pass additional values to
the server if this was an HTTP POST request as you will see later.
The Client Side JSON
Now we are going to create the function that handles the AJAX server
response. This function is also where we get to see JSON in action.
function handleReceiveChat() {
if (receiveReq.readyState == 4) {
//Get a reference to our chat container div for easy access
var chat_div = document.getElementById('div_chat');
//Get the AJAX response and run the JavaScript evaluation function
//on it to turn it into a usable object. Notice since we are passing
//in the JSON value as a string we need to wrap it in parentheses
var response = eval("(" + receiveReq.responseText + ")");
for(i=0;i < response.messages.message.length; i++) {
chat_div.innerHTML += response.messages.message[i].user;
chat_div.innerHTML += ' ' + response.messages.message[i].time + '
';
chat_div.innerHTML += response.messages.message[i].text + '
';
chat_div.scrollTop = chat_div.scrollHeight;
lastMessage = response.messages.message[i].id;
}
mTimer = setTimeout('getChatText();',2000); //Refresh our chat in 2 seconds
}
}
This
function will fire every time the XmlHttpRequest object's readyState
changes, not just when it is complete,
so we need to check to make sure
the readyState = 4 which is complete.
The first line simply gets a reference to DIV that holds all of our
chat messages.
We are simply doing this so we don't have to reference
the DIV with document.getElementById each time.
The next line
is the really cool part, and is the reason I will be using JSON for
AJAX in the future rather than XML.
With JSON, we can use JavaScript's
built-in eval() function to create a usable object that we can
reference with "dot" notation.
Check out the AJAX Chat Tutorial with
XML to see how this would be done using XML instead.
var response = eval("(" + receiveReq.responseText + ")");
What is happening here is the JSON response is transformed into an
object with child arrays using the eval() function.
The
receiveReq.responseText gets the string returned from the AJAX
request.
The other option would be responseXML, which would return an
XML DOM object.
Notice that since we are passing in the responseText to the eval
function as a string we need to wrap it in parentheses.
Otherwise this
would throw an error - it certainly got me more than once.
Now
that we have our AJAX response data stored in a usable JavaScript
object, we can loop through each
of the messages provided and update
our chat DIV.
Let's take one more look at the format our JSON response will be in:
{"messages":
{"message":[
{"id": "17",
"user": "Ryan Smith",
"text": "This is an example of JSON",
"time": "04:41"
},{"id": "18",
"user": "Ryan Smith",
"text": "Here is another Element",
"time": "04:41"
} ]
}
}
As you can see we have a "Root" messages object followed by a message
element with X number of message values.
JSON key value pairs are
seperated by colons (:), and their values are wrapped in quotes. I
think you only need to wrap
the value in quotes if it contains spaces
or special characters, but for good practice, we will just wrap
everything in quotes.
Since we have this data represented as a JavaScript object, we can
access its values including
the length of message values through
standard JavaScript dot notation.
for(i=0;i < response.messages.message.length; i++) {
This line loops through each element. The next three lines add the message values to our chat DIV.
chat_div.innerHTML += response.messages.message[i].user;
chat_div.innerHTML += ' ' + response.messages.message[i].time + '
';
chat_div.innerHTML += response.messages.message[i].text + '
';
Notice how we can access our values through .user .time and .message
rather than some complicated getElementByTag call required with XML.
The next line is a nice little usability addition that automatically
scrolls our DIV to the bottom so the most
recent message is always
visible - thanks for this one Eric.
chat_div.scrollTop = chat_div.scrollHeight;
Finally, we set the last message we received using the same JSON functionality as we did to populate the chat DIV:
lastMessage = response.messages.message[i].id;
Once we finally fall out of our message loop, we will reset the time to
check for any new messages in 2 seconds.
Depending on your server
load, you may want to throttle this even more, although the amount of
data being sent down
the pipe is fairly minimal - probably never never
more than 10K.
mTimer = setTimeout('getChatText();',2000); //Refresh our chat in 2 seconds
Sending a Message
Next we
need to write the code to send a message to the server. We will send
our message as an HTTP "POST"
rather than a "GET" this time since we
will be sending larger amounts of data.
URL's only support a couple of
thousand characters on older browsers,
and the RFC for URL's state you
should try to not go over 255 characters. This will keep us from
getting into trouble with any
long messages getting sent.
Creating a POST AJAX request isn't that much different that creating an AJAX GET request.
//Add a message to the chat server.
function sendChatText() {
if (sendReq.readyState == 4 || sendReq.readyState == 0) {
sendReq.open("POST", 'getChat.php?chat=1&last=' + lastMessage, true);
sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
sendReq.onreadystatechange = handleSendChat;
var param = 'message=' + document.getElementById('txt_message').value;
param += '&name=Ryan Smith';
param += '&chat=1';
sendReq.send(param);
document.getElementById('txt_message').value = '';
}
}
As you can see, it's basically the same code, but instead of passing
the parameters in the URL,
we pass them when we call the
XmlHttpRequest.send method and mark our method as a POST when we
initialize the request.
var param = 'message=' + document.getElementById('txt_message').value;
param += '&name=Ryan Smith';
param += '&chat=1';
sendReq.send(param);
The parameters are & spaced just like in a URL query string. We
are passing three values to the server,
our message text, our name, and
the chat session we are currently in - which is currently hard coded to
my name and 1 for the sake of this tutorial.
Our callback function for this AJAX request is extreamly basic.
//When our message has been sent, update our page.
function handleSendChat() {
//Clear out the existing timer so we don't have
//multiple timer instances running.
clearInterval(mTimer);
getChatText();
}
All we are doing is clearing out the old timer and starting an AJAX request for new messages with the function we already wrote.
Resetting the Chat
We now have everything we need in our HTML and JavaScript to send
message back and forth from the server.
The only thing left for us to
do is add the ability to reset the chat. In a real implementation, you
would probably want to have have the server
"prune" the messages every
so often rather than give the user the ability to clear it out.
//This cleans out the database so we can start a new chat session.
function resetChat() {
if (sendReq.readyState == 4 || sendReq.readyState == 0) {
sendReq.open("POST", 'getChat.php?chat=1&last=' + lastMessage, true);
sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
sendReq.onreadystatechange = handleResetChat;
var param = 'action=reset';
sendReq.send(param);
document.getElementById('txt_message').value = '';
}
}
This function is essentially the same as sending the message, but
instead of sending the message as a parameter, we are sending an action
parameter to reset the chat.
The callback to this function is also very similar to handleSendChat
with the addition of clearing out the text (innerHTML) of our chat DIV.
The last thing we need to do to our HTML page it to add onclick handlers to our HTML buttons.
type="button" name="btn_get_chat" id="btn_get_chat"
value="Refresh Chat" onclick="javascript:getChatText();" />
type="button" name="btn_reset_chat" id="btn_reset_chat"
value="Reset Chat" onclick="javascript:resetChat();" />
/>
type="text" id="txt_message" name="txt_message" style="width: 447px;" />
type="button" name="btn_send_chat" id="btn_send_chat"
value="Send" onclick="javascript:sendChatText();" />
Each button gets pointed to its corresponding JavaScript function.
The Backend
This tutorial will use PHP and MySQL as the backend, but this could
easily be modified to use any server-side language (ASP, ASP.NET, JSP,
CFM, RoR, etc.).
The response is a simple text response so the choice
of language is pretty much up to personal preference.
We only
need the one page since all of our AJAX requests go to the same page.
They are then sorted out based on the parameters sent in the response.
The first thing the backend file does is create some HTTP headers to
keep the users browsers from caching the response.
//Send some headers to keep the user's browser from caching the response.
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/xml; charset=utf-8");
Probably the most important header is the first "Expires" header.
You
can see we set it to a date that has already passed.
Without this
header, IE tends to cache the response regardless of the other headers.
The other header we send is the Content-Type. We don't really need
this since we're sending plain text.
This would be much more important
if we were sending XML like the original AJAX Chat Tutorial.
The next line includes a file that contains our database functions.
I
like to abstract my database functions in a seperate
file in case I
need to change by database connection type (MSSQL, Oracle, etc.).
require('database.php');
This file can be changed to connect to pretty much any database
type with some simple changes to the file.
The first action we perform is to see if a new message was sent in our POST parameters.
//Check to see if a message was sent.
if(isset($_POST['message']) && $_POST['message'] != '') {
$sql = "INSERT INTO message(chat_id, user_id, user_name, message, post_time) VALUES (" .
db_input($_GET['chat']) . ", 1, '" . db_input($_POST['name']) .
"', '" . db_input($_POST['message']) . "', NOW())";
db_query($sql);
}
If a new message was set, then we create a SQL INSERT statement to add
the message to the database.
We use the function db_input - which is
included in the database.php file to escape any quotes or other
dangerous SQL that may have been entered by the user.
You should always cleanse user input before you execute SQL statements
with it.
Acutally you should be using stored procedures or prepared
statements, but that's a little
beyond the scope of what we're doing
here.
Finally we execute the query to add the new message to the database.
Next we check to see if a reset action was sent in our POST
parameters.
If it was we will delete all of the messages with the give
chat_id.
//Check to see if a reset request was sent.
if(isset($_POST['action']) && $_POST['action'] == 'reset') {
$sql = "DELETE FROM message WHERE chat_id = " . db_input($_GET['chat']);
db_query($sql);
}
Obviously if this were a production system you would want to have some
security built around this to keep
others from deleting messages in
chat sessions they didn't belong to,
but since this is just a proof of
concept, we're not going to worry about it.
Creating the Response
Once we have performed any INSERTS or DELETES to the system,
it's time to create our JSON response.
The first thing we'll do is create our JSON root element since this will always be sent back to the server.
//Create the JSON response.
$json = '{"messages": {';
Next, we will check to make sure we recieved a chat room in our request.
//Check to ensure the user is in a chat room.
if(!isset($_GET['chat'])) {
$json .= '"message":[ {';
$json .= '"id": "0",
"user": "Admin",
"text": "You are not currently in a chat session. Enter a chat session here",
"time": "' . date('h:i') . '"
}]';
If we didn't we will send a response informing them they need to enter
one before they can receive any messages.
Once again, in a real system
you would want to do security checks here to ensure the user has
permissions to access the chat room.
If we did receive a chat room id then we will run a SQL query to see if
any new messages have been added since the last request.
} else {
$last = (isset($_GET['last']) && $_GET['last'] != '') ? $_GET['last'] : 0;
$sql = "SELECT message_id, user_name, message, date_format(post_time, '%h:%i') as post_time" .
" FROM message WHERE chat_id = " . db_input($_GET['chat']) . " AND message_id > " . $last;
$message_query = db_query($sql);
If we didn't receive a "last" parameter,
we will assume that the user
hasn't received any messages yet and set this value to zero.
If there are any messages the user hasn't received yet, we will loop through each one and create a JSON message for each.
//Loop through each message and create an XML message node for each.
if(db_num_rows($message_query) > 0) {
$json .= '"message":[ ';
while($message_array = db_fetch_array($message_query)) {
$json .= '{';
$json .= '"id": "' . $message_array['message_id'] .
|