Search


Change Language


Ajax Downloads Home arrow Ajax tutorials arrow Index arrow Tutorials arrow ajax 

Index arrow Tutorials

These are some great tutorials I found on the Net.
arrow ajax

AJAX Chat Tutorial Part 4: IndexController MessageAction() and JSON Encoding
Tuesday, 03 April 2007
Sticking on the server side, we can make a stab at adding a new MessageAction method to our current Controller. This will expect one piece of information: a new chat message. XML has a number of illegal characters you may not use within XML content. We could use the CDATA style enclosures to tell parsers to ignore such characters but unfortunately SimpleXML is not quite up to handling CDATA. Instead we'll simply ensure the message is passed through htmlentities() to turn HTML special characters into their entity equivalents. This has much the same effect at the cost of a little added complexity.
new best online casinos

Adding MessageAction()

Our IndexController will now evolve to:

class IndexController extends Zend_Controller_Action
{

public function IndexAction()
{
/*
* Get View from Registry
*/
$view = Zend_Registry::getInstance()->get('view');

/*
* Set a default Screen Name
* Once a user sets their own screen name
* it will be stored in the user session
*/
if(!isset($_SESSION['chat_screenname']))
{
$view->screenName = 'NewUser';
}
else
{
$view->screenName = $_SESSION['chat_screenname'];
}

/*
* Use a Response object to collate
* any output.
*/
$this->getResponse()->setBody(
$view->render('index.tpl.php')
);
}

public function MessageAction()
{
/*
* Check for Session value chat_lastrefresh (last refresh timestamp)
* otherwise set it to time() - 120 (2 minutes earlier)
* This lets new users see the prior 2 minutes of conversation.
*/
if(!isset($_SESSION['chat_lastrefresh']))
{
$_SESSION['chat_lastrefresh'] = time() - 120;
}

/*
* At this point the user has submitted a new chat message
* without setting a screen name. Since the default is in
* place, we'll simply add to the session for future use.
*/
if(!isset($_SESSION['chat_screenname']))
{
$_SESSION['chat_screenname'] = 'NewUser';
}

/*
* Grab the message text from the relevant superglobal variable
*/
$message = isset($_GET['message']) ? $_GET['message'] : '';

/*
* The message value must not exceed 255 characters.
* User data must always be filtered and validated.
* We allow empty messages, and assume an empty message
* is a simple request to refresh the chat panel without
* adding a new user message.
*/
if(strlen($message) > 255)
{
throw new Exception('Invalid message! Must be 255 characters or less.');
}

/*
* Create the XML file if not existing! Directory must be writeable...
*/
if(!file_exists('./data/chat.xml'))
{
$newXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><chat></chat>';
file_put_contents('./data/chat.xml', $newXML);
}

/*
* Load the current XML store
*/
$xml = simplexml_load_file('./data/chat.xml');

/*
* Only store the message if it's not empty!
*/
if(!empty($message))
{
/*
* First we entitise special characters so they do
* not create future XML parsing errors. XML has several
* invalid characters like <>&"' which can only be used
* as entities, or when placed in a CDATA section.
*
* Although not fatal, SimpleXML will simply reverse
* entities for both double and single quotes before
* writing the XML to file. The XML will therefore
* be illegal, but will not create any problem for
* SimpleXML when parsing. Odd behaviour...;)
*/
$entity_message = htmlspecialchars($message, ENT_QUOTES);

/*
* Add new <message> element to XML file
*/
$newMessage = $xml->addChild('message');

/*
* Add our data to the new <message> element.
* Requires SimpleXML improvements since PHP 5.1.3!
*/
$newMessage->addChild('author', $_SESSION['chat_screenname']);
$newMessage->addChild('timestamp', time());
$newMessage->addChild('text', $entity_message);
}

/*
* Add the file contents to our Response!
* Set header to text/xml for browsers.
*/
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->setBody(
$xml->asXML()
);
}

}

Note that we've referred to the $_GET superglobal (i.e. data is from a request using the GET method) and are echoing rather than saving the XML. In order to keep track of the user's current screen name and the timestamp for when their chat pane was last refreshed we have added two values to the $_SESSION array: chat_screenname and chat_lastrefresh. Session data is stored between requests on the server - we started our session in the index.php bootstrap file by calling session_start().

To access the new method use a url similar to http://www.example.com/chat-tutorial/index/message?message=somemessagetext. This will call the MessageAction() method of the IndexController class with the query string as attached accessible from the $_GET superglobal.

The result should be XML output containing the new message, with the user "NewUser" and the current UNIX timestamp. If you submit an empty message the current XML document will be returned without any changes. We're setting the text/xml Content-Type header so browsers will correctly render the XML (Firefox 1.5+ specifically requires this).

Assuming this works (should be!) we can make a small amendment to save the file rather than echo it. Change

/*
* Add the file contents to our Response!
* Set header to text/xml for browsers.
*/
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->setBody(
$xml->asXML()
);

to
php

/*
* Save the file!
*/
$xml->asXML('./data/chat.xml');

Any of our new requests via the GET method will now store the message (unless empty) to the XML file. Try it a few times and open the XML file to double check.

Told you so :). Also bear in mind, in a typical application we would preferably use POST requests. GET is just easier for debugging without mucking around with POST requests from a browser.

We're not done quite yet. XML is the format we're using for storage but for our chat application we will be using a second format to transfer the new message data back to a waiting AJAX handler function. This new format is JSON (Javascript Object Notation) and generating it from the XML data for new messages is covered in the next section.

JSON Encoding with Zend_Json

Transferring data to the browser on receipt of an XMLHttpRequest can be done using any number of formats from plain text to XML. AJAX (where the X stands for XML) was originally named as such because XML was the transport format of choice at the time. This does not necessarily make XML the most attractive format. Manipulating XML using the Javascript DOM is a complex task.

JSON (Javascript Object Notation) is a lightweight data interchange format which can be evaluated by Javascript using the eval() procedure. It's a simple subset of the object literal notation of Javascript and can represent data such as arrays (among other things). Unlike XML it's a lot simpler, requires no markup and minimal processing.

Because of its simplicity, JSON is quite easy to encode and decode in PHP and most other programming languages while XML requires a much heftier set of functionality. The only PHP methods we, in our chat application, need know are the static methods Zend_Json::encode() and Zend_Json::decode(). Given we're not sending lots of data to the server, we can narrow that down to Zend_Json::encode(). The Zend_Json class forms part of the Zend Framework.

Note: As of PHP 5.2.0, the new json_encode() and json_decode() functions are also available. I have not yet tested how these match up to "Zend_Json":http://framework.zend.com/manual/en/zend.json.html's static methods. Initial testing shows these functions may require content to be encoded to utf8 (see utf8_encode() in the PHP manual) BEFORE calling the functions. "Zend_Json":http://framework.zend.com/manual/en/zend.json.html handles all content without any difficulty - the chat application will support multi-byte characters without any changes (assuming the particular charset is supported by your browser!).

Encoding New Messages to JSON

As we've already seen in our section on SimpleXML we already have the means to extract from our XML all messages stored since the user's last refresh/message submission. Using XPATH this is as simple as:

$xml = simplexml_load_file('./data/chat.xml');

$lastRefresh = '1161790302';

/*
* Select all message elements with a timestamp greater than 1161790302
* i.e. XPATH "/chat/message[timestamp>1161790302]"
*/
$newMessages = $xml->xpath('/chat/message[timestamp>' . $lastRefresh . ']');

foreach($newMessages as $message)
{
$timestamp = $message->timestamp;
$author = $message->author;
$text = $message->text;
echo 'Date: ', date($timestamp), ' Author: ', $author, ' Message: ', $text, '<br />';
}

Going a step further; in order for us to send these new messages to the browser we need to add them to an array which is then encoded into the JSON format. Building the response will require using XPATH to gather the data and then using a for() statement (where $i is the number of messages) to build a PHP array.

We can then encode this PHP array in the JSON format using Zend_Json".

/**
* $xml already holds our XML data
* The last refresh timestamp is stored in user's Session Data
*/
$newMessages = $xml->xpath('/chat/message[timestamp>'
. $_SESSION['chat_lastrefresh'] . ']');
$newMessageCount = count($newMessages);
$phpMessageArray = array();

/*
* Build a PHP array of new messages.
*
* We must cast each XML element to String (since it's only
* done automatically if we attempt to use the element as a string
* and PHP autmatically calls __toString() on a SimpleXMLElement
* object, e.g. echo().
*
* Escape all expected text output based on user input for
* htmlentities. (Security precaution.)
*/
for($i=0;$i<$newMessageCount;++$i)
{
$phpMessageArray[$i]['author'] =
htmlentities((string) $newMessages[$i]->author, ENT_QUOTES, 'utf-8');
$phpMessageArray[$i]['timestamp'] =
(string) $newMessages[$i]->timestamp;
$phpMessageArray[$i]['text'] =
htmlentities((string) $newMessages[$i]->text, ENT_QUOTES, 'utf-8');
}

/*
* Encode the PHP array into JSON
*/
require_once 'Zend/Json.php';
$responseJSON = Zend_Json::encode($phpMessageArray);

The Updated IndexController

Keeping the above in mind, we can revisit our IndexController's MessageAction() method again. In this case, we add the above code block and also ensure we reset the user's $_SESSION['chat_lastrefresh'] value to the current timestamp.

With this in place, we have substantially completed the server side coding for basic operation of a chat application. At a later time we will add support for letting a user set a screen name.

class IndexController extends Zend_Controller_Action
{

public function IndexAction()
{
/*
* Get View from Registry
*/
$view = Zend_Registry::getInstance()->get('view');

/*
* Set a default Screen Name
* Once a user sets their own screen name
* it will be stored in the user session
*/
if(!isset($_SESSION['chat_screenname']))
{
$view->screenName = 'NewUser';
}
else
{
$view->screenName = $_SESSION['chat_screenname'];
}

/*
* Lets use a Response object to collate
* any output.
*/
$this->getResponse()->setBody(
$view->render('index.tpl.php')
);
}

public function MessageAction()
{
/*
* Check for Session value chat_lrefresh (last refresh timestamp)
* otherwise set it to time() - 120 (2 minutes earlier)
*/
if(!isset($_SESSION['chat_lastrefresh']))
{
$_SESSION['chat_lastrefresh'] = time() - 1200;
}

/*
* At this point the user has submitted a new chat message
* without setting a screen name. Since the default is in
* place, we'll simply add to the session for future use.
*/
if(!isset($_SESSION['chat_screenname']))
{
$_SESSION['chat_screenname'] = 'NewUser';
}

/*
* Grab the message text from the relevant superglobal variable
*/
$message = isset($_GET['message']) ? $_GET['message'] : '';

/*
* The message value must exist and not exceed 255 characters
* User data must always be filtered and validated.
* We allow empty messages, and assume an empty message
* is a simple request to refresh the chat panel without
* adding a new user message.
*/
if(strlen($message) > 255)
{
throw new Exception('Invalid message! Must be 255 characters or less.');
}

/*
* Create the XML file if not existing! Directory must be writeable...
*/
if(!file_exists('./data/chat.xml'))
{
$newXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><chat></chat>';
file_put_contents('./data/chat.xml', $newXML);
}

/*
* Load the current XML store
*/
$xml = simplexml_load_file('./data/chat.xml');

/*
* Only store the message if it's not empty!
*/
if(!empty($message))
{
/*
* First we entitise special characters so they do
* not create future XML parsing errors. XML has several
* invalid characters like <>&"' which can only be used
* as entities, or when placed in a CDATA section.
*
* Although not fatal, SimpleXML will simply reverse
* entities for quotes and single apostrophes before
* writing the XML to file. The XML will therefore
* be illegal, but will not create any problem for
* SimpleXML when parsing. Odd behaviour...;)
*/
$entity_message = htmlspecialchars($message, ENT_QUOTES);

/*
* Add new <message> element to XML file
*/
$newMessage = $xml->addChild('message');

/*
* Add our data to the new <message> element
*/
$newMessage->addChild('author', $_SESSION['chat_screenname']);
$newMessage->addChild('timestamp', time());
$newMessage->addChild('text', $entity_message);

/*
* Write updated XML to file!
*/
$xml->asXML('./data/chat.xml');
}

/**
* $xml already holds our XML data
* The last refresh timestamp is stored in user's Session Data
*/
$newMessages = $xml->xpath('/chat/message[timestamp>' . $_SESSION['chat_lastrefresh'] . ']');
$newMessageCount = count($newMessages);
$phpMessageArray = array();

/*
* Build a PHP array of new messages.
* We must cast each XML element to String (since it's only
* done automatically if we attempt to use the element as a string
* and PHP autmatically casts to String, e.g. echo().
*/
for($i=0;$i<$newMessageCount;++$i)
{
$phpMessageArray[$i]['author'] = (string) $newMessages[$i]->author;
$phpMessageArray[$i]['timestamp'] = (string) $newMessages[$i]->timestamp;
$phpMessageArray[$i]['text'] = (string) $newMessages[$i]->text;
}

/*
* Reset the user's Session chat_lastrefresh value
*/
$_SESSION['chat_lastrefresh'] = time();

/*
* Encode the PHP array into JSON
*/
require_once 'Zend/Json.php';
$responseJSON = Zend_Json::encode($phpMessageArray);

/*
* Set the Response for the user testing this
* or for the AJAX handler on the client.
*/
$this->getResponse()->setHeader('Content-Type', 'text/plain');
$this->getResponse()->setBody($responseJSON);
}

}

If you make additional requests through the browser as before, you should see that the XML output has been replaced with a block of JSON encoded data in the form:

[{"author" : "NewUser", "timestamp" : 1161876001, "text" : "HelloWorld"}]

On the client side, this will simplify using the data since we can use the Javascript eval() procedure to evaluate this string into Javascript structures. This is far simpler than sending XML and using the Javascript DOM to process it. We'll see how simple this is in the next section where we take a look at our client side Javascript.

On security, attempting a common XSS exploit like adding a message of "I'm an annoying spacemonkey! <script>window.alert('XSS');</script>" which contains a javascript function, should in any JSON notation be replaced with an entitised version. This ensures it's printed literally and can't run on another user's browser.



 
There are 279 free ajax scripts and 31 categories in our directory





Lost Password?
No account yet? Register
We have 31 guests online