Simple Chat

With about 20 lines of PHP and about 40 lines of JavaScript

Back to the project page.

Source

This is the source code if this file, including the PHP code:


<?php

/**
 * Simple chat example by Stephan Soller
 * See http://arkanis.de/projects/simple-chat/
 */

// Name of the message buffer file. You have to create it manually with read and write permissions for the webserver.
$messages_buffer_file 'messages.json';
// Number of most recent messages kept in the buffer
$messages_buffer_size 10;

if ( isset(
$_POST['content']) and isset($_POST['name']) )
{
    
// Open, lock and read the message buffer file
    
$buffer fopen($messages_buffer_file'r+b');
    
flock($bufferLOCK_EX);
    
$buffer_data stream_get_contents($buffer);
    
    
// Append new message to the buffer data or start with a message id of 0 if the buffer is empty
    
$messages $buffer_data json_decode($buffer_datatrue) : array();
    
$next_id = (count($messages) > 0) ? $messages[count($messages) - 1]['id'] + 0;
    
$messages[] = array('id' => $next_id'time' => time(), 'name' => $_POST['name'], 'content' => $_POST['content']);
    
    
// Remove old messages if necessary to keep the buffer size
    
if (count($messages) > $messages_buffer_size)
        
$messages array_slice($messagescount($messages) - $messages_buffer_size);
    
    
// Rewrite and unlock the message file
    
ftruncate($buffer0);
    
rewind($buffer);
    
fwrite($bufferjson_encode($messages));
    
flock($bufferLOCK_UN);
    
fclose($buffer);
    
    
// Optional: Append message to log file (file appends are atomic)
    //file_put_contents('chatlog.txt', strftime('%F %T') . "\t" . strtr($_POST['name'], "\t", ' ') . "\t" . strtr($_POST['content'], "\t", ' ') . "\n", FILE_APPEND);
    
    
exit();
}

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Simple Chat</title>
    <script type="text/javascript" src="jquery-1.4.2.min.js"></script>
    <script type="text/javascript">
        // <![CDATA[
        $(document).ready(function(){
            // Remove the "loading…" list entry
            $('ul#messages > li').remove();
            
            $('form').submit(function(){
                var form = $(this);
                var name =  form.find("input[name='name']").val();
                var content =  form.find("input[name='content']").val();
                
                // Only send a new message if it's not empty (also it's ok for the server we don't need to send senseless messages)
                if (name == '' || content == '')
                    return false;
                
                // Append a "pending" message (not yet confirmed from the server) as soon as the POST request is finished. The
                // text() method automatically escapes HTML so no one can harm the client.
                $.post(form.attr('action'), {'name': name, 'content': content}, function(data, status){
                    $('<li class="pending" />').text(content).prepend($('<small />').text(name)).appendTo('ul#messages');
                    $('ul#messages').scrollTop( $('ul#messages').get(0).scrollHeight );
                    form.find("input[name='content']").val('').focus();
                });
                return false;
            });
            
            // Poll-function that looks for new messages
            var poll_for_new_messages = function(){
                $.ajax({url: 'messages.json', dataType: 'json', ifModified: true, timeout: 2000, success: function(messages, status){
                    // Skip all responses with unmodified data
                    if (!messages)
                        return;
                    
                    // Remove the pending messages from the list (they are replaced by the ones from the server later)
                    $('ul#messages > li.pending').remove();
                    
                    // Get the ID of the last inserted message or start with -1 (so the first message from the server with 0 will
                    // automatically be shown).
                    var last_message_id = $('ul#messages').data('last_message_id');
                    if (last_message_id == null)
                        last_message_id = -1;
                    
                    // Add a list entry for every incomming message, but only if we not already inserted it (hence the check for
                    // the newer ID than the last inserted message).
                    for(var i = 0; i < messages.length; i++)
                    {
                        var msg = messages[i];
                        if (msg.id > last_message_id)
                        {
                            var date = new Date(msg.time * 1000);
                            $('<li/>').text(msg.content).
                                prepend( $('<small />').text(date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + ' ' + msg.name) ).
                                appendTo('ul#messages');
                            $('ul#messages').data('last_message_id', msg.id);
                        }
                    }
                    
                    // Remove all but the last 50 messages in the list to prevent browser slowdown with extremely large lists
                    // and finally scroll down to the newes message.
                    $('ul#messages > li').slice(0, -50).remove();
                    $('ul#messages').scrollTop( $('ul#messages').get(0).scrollHeight );
                }});
            };
            
            // Kick of the poll function and repeat it every two seconds
            poll_for_new_messages();
            setInterval(poll_for_new_messages, 2000);
        });
        // ]]>
    </script>
    <style type="text/css">
        html { margin: 0em; padding: 0; }
        body { margin: 2em; padding: 0; font-family: sans-serif; font-size: medium; color: #333; }
        h1 { margin: 0; padding: 0; font-size: 2em; }
        p.subtitle { margin: 0; padding: 0 0 0 0.125em; font-size: 0.77em; color: gray; }
        
        ul#messages { overflow: auto; height: 15em; margin: 1em 0; padding: 0 3px; list-style: none; border: 1px solid gray; }
        ul#messages li { margin: 0.35em 0; padding: 0; }
        ul#messages li small { display: block; font-size: 0.59em; color: gray; }
        ul#messages li.pending { color: #aaa; }
        
        form { font-size: 1em; margin: 1em 0; padding: 0; }
        form p { position: relative; margin: 0.5em 0; padding: 0; }
        form p input { font-size: 1em; }
        form p input#name { width: 10em; }
        form p button { position: absolute; top: 0; right: -0.5em; }
        
        ul#messages, form p, input#content { width: 40em; }
        
        pre { font-size: 0.77em; }
    </style>
    <meta name="author" content="Stephan Soller" />
</head>
<body>

<h1>Simple Chat</h1>
<p class="subtitle">With about 20 lines of PHP and about 40 lines of JavaScript</p>

<ul id="messages">
    <li>loading…</li>
</ul>

<form action="<?= htmlentities($_SERVER['PHP_SELF'], ENT_COMPAT'UTF-8'); ?>" method="post">
    <p>
        <input type="text" name="content" id="content" />
    </p>
    <p>
        <label>
            Name:
            <input type="text" name="name" id="name" value="Anonymous" />
        </label>
        <button type="submit">Send</button>
    </p>
</form>

<p>Back to the <a href="./">project page</a>.</p>

<h2 id="source">Source</h2>

<p>This is the source code if this file, including the PHP code:</p>

<pre><? show_source(__FILE__); ?></pre>

</body>
</html>