Tutorial: Making a Shoutbox with PHP and jQuery

Demo Download

In this tutorial, we are going to build a shout box with PHP and jQuery, which allows visitors of your website to leave short comments to one another. Shouts will be stored on the server as files, no database like MySQL will be required. We are going to use two PHP libraries to make things easier - Flywheel for storing the shouts as json files and RelativeTime for creating human readable relative time stamps. We will be using Composer to install these libraries.

On the client side, we are using plain jQuery code, and the Emoji One library, which is a free project and library for adding pretty emojis to web apps. Let's begin!

Running the shoutbox

You can grab the source code from the download button above. It has plenty of comments and is easy to follow. To run it, simply upload it to your web hosting space or add it to the apache htdocs folder if you run something like XAMPP or MAMP. Then, open http://localhost in your browser (or your website, if you uploaded it to your hosting space). Here are a few things to look for:

  • The zip files already contains the dependencies, so you don't need to install Composer. This makes it easy to get started with the code - simply upload it and use it!
  • Make sure that the data/shouts directory exists and is writable. Otherwise you will see errors in your log file and no shouts will be stored. You might need to chmod it to 777 if you keep seeing errors.

The HTML

Let's start with index.html. It is a regular HTML5 document, which includes our JavaScript libraries, scripts and stylesheets. Here are the parts relevant to the shoutbox:

index.html

<div class="shoutbox">

    <h1>Shout box <img src='./assets/img/refresh.png'/></h1>

    <ul class="shoutbox-content"></ul>

    <div class="shoutbox-form">
        <h2>Write a message <span>×</span></h2>

        <form action="./publish.php" method="post">
            <label for="shoutbox-name">nickname </label> <input type="text" id="shoutbox-name" name="name"/>
            <label class="shoutbox-comment-label" for="shoutbox-comment">message </label> <textarea id="shoutbox-comment" name="comment" maxlength='240'></textarea>
            <input type="submit" value="Shout!"/>
        </form>
    </div>

</div>

With JavaScript we will insert the published shouts into the <ul> element. The form is hidden by default, and only revealed when the "Write a message" header is clicked.

shout-box-php-jquery.png
Shoutbox with PHP and jQuery

The JavaScript Code

And here is our script.js, which makes the above HTML work:

assets/js/script.js

$(function(){

    // Storing some elements in variables for a cleaner code base

    var refreshButton = $('h1 img'),
        shoutboxForm = $('.shoutbox-form'),
        form = shoutboxForm.find('form'),
        closeForm = shoutboxForm.find('h2 span'),
        nameElement = form.find('#shoutbox-name'),
        commentElement = form.find('#shoutbox-comment'),
        ul = $('ul.shoutbox-content');

    // Replace :) with emoji icons:
    emojione.ascii = true;

    // Load the comments.
    load();

    // On form submit, if everything is filled in, publish the shout to the database

    var canPostComment = true;

    form.submit(function(e){
        e.preventDefault();

        if(!canPostComment) return;

        var name = nameElement.val().trim();
        var comment = commentElement.val().trim();

        if(name.length && comment.length && comment.length < 240) {

            publish(name, comment);

            // Prevent new shouts from being published

            canPostComment = false;

            // Allow a new comment to be posted after 5 seconds

            setTimeout(function(){
                canPostComment = true;
            }, 5000);

        }

    });

    // Toggle the visibility of the form.

    shoutboxForm.on('click', 'h2', function(e){

        if(form.is(':visible')) {
            formClose();
        }
        else {
            formOpen();
        }

    });

    // Clicking on the REPLY button writes the name of the person you want to reply to into the textbox.

    ul.on('click', '.shoutbox-comment-reply', function(e){

        var replyName = $(this).data('name');

        formOpen();
        commentElement.val('@'+replyName+' ').focus();

    });

    // Clicking the refresh button will force the load function

    var canReload = true;

    refreshButton.click(function(){

        if(!canReload) return false;

        load();
        canReload = false;

        // Allow additional reloads after 2 seconds
        setTimeout(function(){
            canReload = true;
        }, 2000);
    });

    // Automatically refresh the shouts every 20 seconds
    setInterval(load,20000);

    function formOpen(){

        if(form.is(':visible')) return;

        form.slideDown();
        closeForm.fadeIn();
    }

    function formClose(){

        if(!form.is(':visible')) return;

        form.slideUp();
        closeForm.fadeOut();
    }

    // Store the shout in the database

    function publish(name,comment){

        $.post('publish.php', {name: name, comment: comment}, function(){
            nameElement.val("");
            commentElement.val("");
            load();
        });

    }

    // Fetch the latest shouts

    function load(){
        $.getJSON('./load.php', function(data) {
            appendComments(data);
        });
    }

    // Render an array of shouts as HTML

    function appendComments(data) {

        ul.empty();

        data.forEach(function(d){
            ul.append('<li>'+
                '<span class="shoutbox-username">' + d.name + '</span>'+
                '<p class="shoutbox-comment">' + emojione.toImage(d.text) + '</p>'+
                '<div class="shoutbox-comment-details"><span class="shoutbox-comment-reply" data-name="' + d.name + '">REPLY</span>'+
                '<span class="shoutbox-comment-ago">' + d.timeAgo + '</span></div>'+
            '</li>');
        });

    }

});

The Emoji One library has version for both JavaScript and PHP. In the appendComments method, we use the emojione.toImage() function to convert all typed-out smileys into emoji. See all functions that are supported, and check out this handy emoji code website. Now that the frontend is ready, let's move on to the backend.

The PHP Code

We have two files - publish.php and load.php. The first accepts a POST request for storing shouts in the data store, and the second returns the 20 latest shouts. These files are not opened directly by visitors - they only handle AJAX requests.

publish.php

<?php

// Include our composer libraries
require 'vendor/autoload.php';

// Configure the data store

$dir = __DIR__.'/data';

$config = new \JamesMoss\Flywheel\Config($dir, array(
    'formatter' => new \JamesMoss\Flywheel\Formatter\JSON,
));

$repo = new \JamesMoss\Flywheel\Repository('shouts', $config);

// Store the posted shout data to the data store

if(isset($_POST["name"]) && isset($_POST["comment"])) {

    $name = htmlspecialchars($_POST["name"]);
    $name = str_replace(array("\n", "\r"), '', $name);

    $comment = htmlspecialchars($_POST["comment"]);
    $comment = str_replace(array("\n", "\r"), '', $comment);

    // Storing a new shout

    $shout = new \JamesMoss\Flywheel\Document(array(
        'text' => $comment,
        'name' => $name,
        'createdAt' => time()
    ));

    $repo->store($shout);

}

Here we directly use the Flywheel library we mentioned in the beginning. Once it is configured, you can store any type of data, which will be written as a JSON file in the data/shouts folder. Reading these shouts is done in load.php:

load.php

<?php

require 'vendor/autoload.php';

// If you want to delete old comments, make this true. We use it to clean up the demo.
$deleteOldComments = false;

// Setting up the data store

$dir = __DIR__.'/data';

$config = new \JamesMoss\Flywheel\Config($dir, array(
    'formatter' => new \JamesMoss\Flywheel\Formatter\JSON,
));

$repo = new \JamesMoss\Flywheel\Repository('shouts', $config);

// Delete comments which are more than 1 hour old if the variable is set to be true.

if($deleteOldComments) {

    $oldShouts = $repo->query()
                ->where('createdAt', '<', strtotime('-1 hour'))
                ->execute();

    foreach($oldShouts as $old) {
        $repo->delete($old->id);
    }

}

// Send the 20 latest shouts as json

$shouts = $repo->query()
        ->orderBy('createdAt ASC')
        ->limit(20,0)
        ->execute();

$results = array();

$config = array(
    'language' => '\RelativeTime\Languages\English',
    'separator' => ', ',
    'suffix' => true,
    'truncate' => 1,
);

$relativeTime = new \RelativeTime\RelativeTime($config);

foreach($shouts as $shout) {
    $shout->timeAgo = $relativeTime->timeAgo($shout->createdAt);
    $results[] = $shout;
}

header('Content-type: application/json');
echo json_encode($results);

We've included code which deletes shouts older than an hour. We use this feature to keep the demo clean. You can enable it if you wish. After selecting the shouts, we are also calculating the human-readable relative time stamp with the RelativeTime library.

With this our shoutbox is ready! You can embed it in your website, customize it and change the code any way you please. We hope you like it!

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Really nice, thank you! :)

Awesome!! a db-less mini-chat!!! thanks for share!!! ;)

I did everything but it doesnt work.

Take a look: http://aaa.antalyabalikcilik.com.tr/

Martin Angelov

In Chrome's Developer Tools, it shows that your PHP script is throwing a 500 error. Do you have access to your PHP error logs? You should find an error message there. Contact your web host if you can't access your logs.

Alessio Alessandro Web

Hi, you must set in ( load.php ) the default time zone!
ex: " date_default_timezone_set('Europe/Rome'); "
copy this code and will be work! ;) trust!

How did you change the date_default_timezone?

Satheeshkumar

Wonderful plugin. Nice :)

Do you have a way to add the ability to upload a photo?

How I can delete messages, please give me a hint.

  1. page for admin
  2. each post have link "delete"
    I need id field for every post to make requests or what?
    Looking for easy solution :)
Lena Antonieff

Good write-up. I absolutely appreciate this site. Keep it up!

Tnx for this app i also have problem when i deploy it on my website, it works on localhost... but in my site it does not work.

I set the timezone, but wont work in IE 11 an Edge. No refresh after submit.

This work:

insert the following at the end of the script.js

$(document).ready(function()
{
$.ajaxSetup ({
// Disable caching of AJAX responses */
cache: false
});
});

Stephanie Hallberg

We’ve included code which deletes shouts older than an hour. We use this feature to keep the demo clean. You can enable it if you wish.

"So how ti NOT delete within one hour. and what code u mean? :)

btw. the shoutbox looks awesome. so small, clean and customizable =)