AJAX Chat Tutorial Part 5: The Javascript, Sending Chat Messages, Screen Name Changes

December 26, 2006

Tutorials, Zend Framework

Sending Chat Messages

One aspect of writing AJAX enabled applications is that one can no longer pass off Javascript as an afterthought. If you’re intent on creating dynamic web applications using AJAX, visual effects, and funky new age interfaces then you’ll just have to start recognising that Javascript is going to be a primary development language.

An introduction to Javascript is out of this tutorial’s scope, but I’ll hit the high points as I move along.

Initiating an XHR Request

XMLHttpRequest (hereafter dubbed “XHR”) is a native browser object in most modern browsers with the exception of Internet Explorer 6 where it is an ActiveX object. It allows a scripting language like Javascript to create background connections to the server which do not reload the current web page. Because accessing the XHR is a complicated process, I’ll skip the fine details and introduce Prototype’s Ajax.Request class which buries the detail behind a simple interface.

Using AJAX with Prototype is covered in detail at http://www.sergiopereira.com/articles/prototype.js.html#UsingAjax.

Before we return to our chat application’s HTML, Ajax.Request makes using XHR a simple procedure. You create an instance of the object, set the url, parameter and option values and finally define a function to handle the response (in our case, a JSON encoded array of new messages to be appended to the chat pane in our HTML.

An example usage is:

var message = 'Hello World!';
var myAjax = new Ajax.Request(
            'controller/action', // relative uri to server 
            {
                method: 'get',
                parameters: 'message=' + encodeURIComponent(message)
                onComplete: alertResponse
            }
);
 
function alertResponse(reply)
{
    // just print the JSON notation response in an alert message
    alert('JSON: ' + reply.responseText);
}

In this simple example, we are issuing a request using the GET method with a message parameter (i.e. PHP will pick it up as $_GET['message']). When a response from the server is received, the Ajax.Request object will pass an instance of itself as a parameter to the handler function – alertResponse() – which we have named “reply”. The instance has several properties, but the main one we’ll use is responseText which contains the plain text equivalant of the reponse body, i.e. our JSON encoded data (assume we used Zend_Json to encode the response).

Triggering an AJAX Request for New Chat Messages

We’re about ready to return to the client side!

In order to trigger an AJAX request to submit a new chat message, we will add an onclick event to the “Say It!” button in our HTML template. The onclick event will be set up to call a javascript function called sendMessage().

The updated HTML section is:

<div id="control">
            <p>Your current username is: 
            <?php echo $this->escape($this->screenName) ?>
            </span>.

Chat:  <input type="text" value="" id="textmessage" size="50" maxlength="255" /> <input type="button" value="Say it!" onclick="sendMessage()" /> <br /> <br /> Change Screen Name:  <input type="text" value="" id="changename" size="15" maxlength="32" /> <input type="button" value="Change!" /> </div>

Adding saveMessage()

The saveMessage() function will be saved in our ./javascript/chat.js file.

function sendMessage ()
{
    var requestUrl = 'index/message';
    var message = $F('textmessage');
    try
    {
        var request = new Ajax.Request(
            requestUrl,
            {
                method: 'get',
                parameters: 'message=' + encodeURIComponent(message),
                onComplete: handleRefresh
            }
        );
    }
    catch (e)
    {
        alert('Error: ' + e.toString());
    }
}

Let’s look at this function in more detail.

sendMessage() first sets up two local variables. requestUrl holds the url to which the AJAX request will be sent. In our case, it points to index/message, i.e. the MessageAction method on the IndexController class we completed earlier.

The message variable is set to the value of the chat message we intend sending. Since the user has typed this into an input element with id “textmessage”, we use Prototype’s shortcut $F() method to grab the value. Since we’re just using input elements to hold input, but using Javascript and XHR to submit it, we don’t require a parent form element.

We then initiate the AJAX request using Prototype’s Ajax.Request class. We’ve put this section within a try/catch block so any errors are caught and alerted to the user. The error handling is not very detailed – just enough to tell us if something goes wrong. A full application would implement far superior error handling.

The Ajax.Request class works just as we covered in our XMLHttpRequest introduction. We’re using the GET method for simplicity (to allow us to test the server side PHP from a browser), adding the new message as a parameter, and setting the onComplete handler function to handleRefresh(). Normally the completed application would use POST (it’s slightly more difficult to tamper with).

Adding handleRefresh()

The handleRefresh function is also saved in our chat.js file.

function handleRefresh (reply)
{
    $('textmessage').value = '';
    var newMessages = eval('(' + reply.responseText + ')');
    var messageCount = newMessages.length;
    for (var i=0; i' + 
                newMessages[i].author + ': ' + newMessages[i].text
                + '

'; } $('chatpane').scrollTop = $('chatpane').scrollHeight; }

The first line simple clears the input element of the user’s typed message. The second evaluates the JSON encoded reponse into a Javascript Array. With our array available, we then iterate through all its elements. On each iteration, we add a new paragraph element to the divider with id “chatpane” in our HTML. Prototype’s $() function is a simple shortcut to using:

document.getElementById(‘chatpane’)

Finally, we ensure that the chat pane scrolls to the bottom of the updated list of messages by setting the scrollTop value to the current scrollHeight.

In the above I directly evaluated the JSON response. As a matter of good practice, it’s generally recommended to use a specialised JSON parser to reduce the risk of the response containing tainted user input which could play havok with a client when eval’d. See: http://www.json.org/js.html.

Does It Work?

If you reload the Chat Application in the browser, you should now be able to send messages as NewUser. Of course we can’t always be NewUser! Our next task is allowing the user to change their Screen Name.

Changing Screen Names

If the Javascript for sending new chat messages was not too daunting then what follows will pose no problems. Our first step is to add an additional onclick event to the “Change!” button. The updated HTML follows.

Change Screen Name: 
<input type="text" value="" id="changename" size="15" maxlength="32" />
<input type="button" value="Change!" onclick="changeName()" />

Adding changeName()

The new onclick event calls a javascript function called changeName(). Again, this will be stored in our chat.js file.

function changeName ()
{
    var requestUrl = 'index/name';
    var newName = $F('changename');
    try
    {
        var request = new Ajax.Request(
            requestUrl,
            {
                method: 'get',
                parameters: 'name=' + encodeURIComponent(newName),
                onComplete: handleChangeName
            }
        );
    }
    catch (e)
    {
        alert('Error: ' + e.toString());
    }
}

The function is almost identical to sendMessage() with a few differences. We’ve changed the requestUrl variable to index/name, i.e. we will need to add a NameAction method to the IndexController class. Let’s do that now. A little later we’ll look at the new handler function handleChangeName().

Adding NameAction() to IndexController.php

Our quick pitstop back on IndexController.php adds the following method.

public function NameAction()
    {
        /*
         * Load the Zend Framework's Filter_Input class
         */
        require_once 'Zend/Filter/Input.php';
        $get = new Zend_Filter_Input($_GET);
 
        /*
         * Ensure the new screen name is an expected value!
         * We assume it must contain the ASCII a-Z0-9
         * alphanumeric characters only
         * Error gets printed to screen instead of name.
         */
        if(!$clean_name = $get->testAlnum('name'))
        {
            $this->getResponse()->setHeader('Content-Type', 'text/plain');
            $this->getResponse()->setBody('Error: Invalid Screen Name');
            return;
        }
 
        /* Defense in Depth. The Zend Framework is likely
         * reliable - but you just never know. Escape all
         * potential output JIC (just in case).
         */
        $escaped_clean_name = htmlentities($clean_name, ENT_QUOTES, 'utf-8');
 
        /*
         * Update the user's session for the new screen name
         */
        $_SESSION['chat_screenname'] = $escaped_clean_name;
 
        /*
         * To tell the client this request was successful echo
         * back the screen name as plain html.
         */
        $this->getResponse()->setBody($escaped_clean_name);
    }

As we noted in the earlier MessageAction() method, the user’s screen name is stored in the user session. In this method, we introduce a new Zend Framework class called Zend_Filter_Input. This class offers methods for filtering and validating input data from GET parameters or forms. We’re going to assume that a screen name must only contain alphanumeric characters, ie. upper and lower case a-Z and the 0-9 numerals.

Instantiating a new Zend_Filter_Input object passing $_GET as the parameter has one major impact. The object will overwrite the $_GET superglobal forcing all access to the GET values to be through the new object. This ensures we’re more likely to properly filter/validate values before use.

In addition we’ll escape the output with htmlentities() just in case the Zend Framework fails for unknown reasons. Highly unlikely, but let’s cover our rear ends anyway ;).

The testAlnum() method will return the “name” value to our script unless it is not alphanumeric, in which case it will return the error message. Assuming the value is valid, we simply store it on the user session as “chat_screenname”. This session value is used to populate the “Your current name is:” area in our HTML.

Updating the HTML for new Screen Names

Back to the client side. Our new handleChangeName() javascript function in chat.js is very simple.

function handleChangeName (reply)
{
    $('changename').value = '';
    $('screenname').innerHTML = reply.responseText;
}
,

About Padraic Brady

Padraic Brady has been an open source PHP developer since 2002. After spending three years developing PHP websites on a part time basis, and being a regular contributor/security reviewer to several online PHP games, he has since gotten a high paying job reviewing internal control systems and applications with a multinational company (who shall go nameless).Since 2004, Padraic has been working in his spare time on several PHP game projects, and has been busy developing a small PHP framework called Partholan. Plans for the future include getting a PEAR account, developing an Ajaxified Chess Server for PHP, and sharing 11+ years of programming experience with anyone willing to listen. He also contributes irregularly to several small PHP projects, the latest being ADOdb Lite.You will most likely find Padraic hanging around on the PHP Developers Network Forum, or lurking on several game forums patiently fielding questions on game design in PHP. Padraic lives in a rural area of County Wicklow, Ireland, where he often entertains the locals with stories of how many different ways websites mangle the weird a-acute character in his name. He desperately looks forward to Unicode support in PHP 6.Padraic maintain a blog over on http://blog.quantum-star.com.

View all posts by Padraic Brady

5 Responses to “AJAX Chat Tutorial Part 5: The Javascript, Sending Chat Messages, Screen Name Changes”

  1. darkvalour Says:

    This code filters out all non alphanumeric characters from the screename.

    public function nameAction()
    {
    /*
    * Load the Zend Framework’s Filter_Input class
    */
    require_once ‘Zend/Filter/Input.php’;

    $filters = array(
    ‘name’ => ‘Alnum’
    );

    $validators = array (
    ‘name’ => ‘Alnum’
    );

    $data = $_GET;
    $input = new Zend_Filter_Input($filters, $validators, $data);

    /*
    * Ensure the new screen name is an expected value!
    * We assume it must contain the ASCII a-Z0-9
    * alphanumeric characters only
    * Error gets printed to screen instead of name.
    */
    if(!$input->isValid(‘name’))
    {
    $this->getResponse()->setHeader(‘Content-Type’, ‘text/plain’);
    $this->getResponse()->setBody(‘Error: Invalid Screen Name’);
    return;
    }

    /* Defense in Depth. The Zend Framework is likely
    * reliable – but you just never know. Escape all
    * potential output JIC (just in case).
    */
    //$escaped_clean_name = htmlentities($clean_name, ENT_QUOTES, ‘utf-8′);
    $escaped_clean_name = $input->getEscaped(‘name’);

    /*
    * Update the user’s session for the new screen name
    */
    $_SESSION['chat_screenname'] = $escaped_clean_name;

    /*
    * To tell the client this request was successful echo
    * back the screen name as plain html.
    */
    $this->getResponse()->setHeader(‘Content-Type’, ‘text/plain’);
    $this->getResponse()->setBody($escaped_clean_name);
    }

  2. darkvalour Says:

    The code listed above for "Your current username is:" is missing the opening span tag: <span id="screenname">.

  3. mustafakaraoglu Says:

    Is there a working example of ajax chat?

  4. hillpatrica Says:

    In college we start study xml language and teacher recommend us this site – <a href="http://xml-for-dummy.memebot.com">xml for dummy</a>

  5. _____anonymous_____ Says:

    The listing for the ‘handleRefresh()’ function is garbled above, because the code is not escaped properly. You can view the document source to get the correct code.