"Who Is Online" Widget With PHP, MySQL & jQuery

Download

For this week's tutorial, we are taking a look at our ever-so-interesting inbox. It all started with a letter from one of our readers a couple of weeks ago:

My boss is always coming into my office asking me to install things on our client's sites. One of the items that's been coming up more and more is "How can we tell who's currently online?" So, the next time you need a tutorial idea, there you go - a php/mysql/jquery 'online users' widget. Oh, and a geomap of each visitor would be super rad too.

Thanks for everything you guys do, Taylor

Taylor, we are always happy when we receive good tutorial ideas, so today we are doing just that - a "Who is online" widget with PHP, MySQL & jQuery. It will display the number of visitors, currently viewing your site, and thanks to Hostip's free IP to location API, it will even be able to detect the country your visitors are from and display it in a slide out panel.

Note: This tutorial is quite old and doesn't work in PHP7 and above. We are keeping it online only as a reference.

Step 1 - XHTML

As usual, we start off with the XHTML part. The code presented here might not look like much, but it is all that we need to show off all the work that has been done by the backend. The widget features a slick slide-out panel with all the geolocation data, shown on mouse over.

demo.html

<div class="onlineWidget">

<div class="panel">

    <!-- Fetched with AJAX: -->

    <div class="geoRow">
    <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/us.gif" width="16" height="11"></div>
    <div class="country" title="UNITED STATES">UNITED STATES</div>
    <div class="people">2</div>
    </div>

    <div class="geoRow">
    <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/uk.gif" width="16" height="11"></div>
    <div class="country" title="UNITED KINGDOM">UNITED KINGDOM</div>
    <div class="people">1</div>
    </div>

</div>

<div class="count">8</div>
<div class="label">online</div>
<div class="arrow"></div>
</div>

As you may see from the markup above, the main container div - "onlineWidget" contains the slide-out panel (the div with class name "panel"), the total number of people online (the "count" div), the "online" label and the green arrow to the right.

i1.png

The panel div is dynamically filled by AJAX with the countries with the most visitors currently online. The default content of this div is a rotating gif preloader, which is replaced with the geo data once the AJAX request is complete (usually in less than a second). We will come back to this in a moment.

Step 2 - The Database

Unlike the usual routine, here we are going to take a look at how the database is structured, as it is fundamental to the rest of the script.

All of the widget data is stored into the tz_who_is_online table. It consists of six fields (or columns). The first one - ID, is a standard primary key / auto increment field. After this is the IP field which stores the visitor's IP address (converted to a integer beforehand with the ip2long PHP function).

After this are three fields fetched by Hostip's API - Country, CountryCode and City. The widget is not using the city field at this point, but it is good to have in case somebody wants to implement it. Last is the DT timestamp field, which is updated on every page load and enables us to track who is online (users without a page load in the last 10 minutes have probably left the site).

i3.png

Step 3 - CSS

The widget is (almost) image free, and is only styled with CSS. Lets take a look at the styling, as defined in styles.css. The code is divided in two parts, so it is easier to follow.

who-is-online/styles.css - Part 1

.onlineWidget,.panel{

    /* Styling the widget and the sliding panel at once */

    background-color:#F9F9F9;
    border:2px solid #FFFFFF;
    height:25px;
    padding:4px 8px;
    position:relative;
    width:130px;

    cursor:pointer;

    /* CSS3 rules for rounded corners, box and text shadows: */

    -moz-border-radius:6px;
    -webkit-border-radius:6px;
    border-radius:6px;

    -moz-box-shadow:0 0 3px #CCCCCC;
    -webkit-box-shadow:0 0 3px #CCCCCC;
    box-shadow:0 0 3px #CCCCCC;

    text-shadow:0 2px 0 white;
}

.onlineWidget:hover{
    background-color:#fcfcfc;
}

.onlineWidget:hover .arrow{
    /* Changing the background image for the green arrow on hover: */
    background-position:bottom center;
}

.count{
    /* The total number of people online div */

    color:#777777;
    float:left;
    font-size:26px;
    font-weight:bold;
    margin-top:-3px;
    text-align:center;
    width:30px;
}

.label{
    /* The online label */

    float:left;
    font-size:10px;
    padding:7px 0 0 7px;
    text-transform:uppercase;
}

In the first step above, you can see that we style the widget and the slide-out panel at once. This is to ensure that they have consistent styling which is easy to change later on. Some rules are unique to the panel, however, so we include a individually targeted set of rules in the second part of the code.

We also define the hover state and style the label and count divs.

who-is-online/styles.css - Part 2

.arrow{
    /* The green arrow on the right */

    background:url(img/arrow.png) no-repeat top center;
    position:absolute;
    right:6px;

    width:25px;
    height:25px;
}

.panel{
    /* The slideout panel */

    position:absolute;
    cursor:default;

    bottom:50px;
    left:0;
    height:auto;
    display:none;
    margin:-2px;
    z-index:1000;
}

.preloader{
    /* The rotating gif preloader image */
    display:block;
    margin:10px auto;
}

.geoRow{
    /* The div that contains each country */

    height:16px;
    overflow:hidden;
    padding:2px 0;
}

.flag{
    float:left;
    margin:0 4px;
}

.country, .people{
    float:left;
    font-size:10px;
    padding:2px;
}

.country{
    width:85px;
    overflow:hidden;
}

.people{
    font-weight:bold;
}

In the second part of the file, we style the how the geolocation data that is presented in the slide-out panel, after jQuery fetches it from the back-end. With this we can continue with the next step.

i2.png

Step 4 - PHP

This is where the magic happens. PHP has to keep the database of online users up to date and fetch IP-to-location data from Hostip's API. This is later cached for future use in a cookie on the visitors PC.

who-is-online/online.php

require "connect.php";
require "functions.php";

// We don't want web bots altering our stats:
if(is_bot()) die();

$stringIp = $_SERVER['REMOTE_ADDR'];
$intIp = ip2long($stringIp);

// Checking wheter the visitor is already marked as being online:
$inDB = mysql_query("SELECT 1 FROM tz_who_is_online WHERE ip=".$intIp);

if(!mysql_num_rows($inDB))
{
    // This user is not in the database, so we must fetch
    // the geoip data and insert it into the online table:

    if($_COOKIE['geoData'])
    {
        // A "geoData" cookie has been previously set by the script, so we will use it

        // Always escape any user input, including cookies:
        list($city,$countryName,$countryAbbrev) = explode('|',mysql_real_escape_string(strip_tags($_COOKIE['geoData'])));
    }
    else
    {
        // Making an API call to Hostip:

        $xml = file_get_contents('http://api.hostip.info/?ip='.$stringIp);

        $city = get_tag('gml:name',$xml);
        $city = $city[1];

        $countryName = get_tag('countryName',$xml);
        $countryName = $countryName[0];

        $countryAbbrev = get_tag('countryAbbrev',$xml);
        $countryAbbrev = $countryAbbrev[0];

        // Setting a cookie with the data, which is set to expire in a month:
        setcookie('geoData',$city.'|'.$countryName.'|'.$countryAbbrev, time()+60*60*24*30,'/');
    }

    $countryName = str_replace('(Unknown Country?)','UNKNOWN',$countryName);

    mysql_query("   INSERT INTO tz_who_is_online (ip,city,country,countrycode)
                    VALUES(".$intIp.",'".$city."','".$countryName."', '".$countryAbbrev."')");
}
else
{
    // If the visitor is already online, just update the dt value of the row:
    mysql_query("UPDATE tz_who_is_online SET dt=NOW() WHERE ip=".$intIp);
}

// Removing entries not updated in the last 10 minutes:
mysql_query("DELETE FROM tz_who_is_online WHERE dt<SUBTIME(NOW(),'0 0:10:0')");

// Counting all the online visitors:
list($totalOnline) = mysql_fetch_array(mysql_query("SELECT COUNT(*) FROM tz_who_is_online"));

// Outputting the number as plain text:
echo $totalOnline;

This PHP script is initially called by jQuery in order to populate the count div with the current number of people online. Behind the scenes, however, this script writes the visitor's IP to the database and resolves their IP-to-location data.

This is the best strategy in organizing the back-end, as we keep the calls to the API (which are quite time expensive) distributed to each user as they visit the site for the first time.

The other alternative would be to store only the IPs of the visitors and queue the geolocation data once the panel is shown. This would mean to resolve a huge number of IPs simultaneously, which would make the script unresponsive and get us black-listed from the API. Totally not cool.

You can queue the Hostip's API by opening a connection to a URL similar to this: http://api.hostip.info/?ip=128.128.128.128. It returns a valid XML response, which contains all sorts of data, including a country and city name associated with the IP, country abbreviation and even absolute coordinates. We are fetching this data with the PHP file_get_contents() function and extracting the bits of information we need.

who-is-online/geodata.php

require "connect.php";
require "functions.php";

// We don't want web bots accessing this page:
if(is_bot()) die();

// Selecting the top 15 countries with the most visitors:
$result = mysql_query(" SELECT countryCode,country, COUNT(*) AS total
                        FROM tz_who_is_online
                        GROUP BY countryCode
                        ORDER BY total DESC
                        LIMIT 15");

while($row=mysql_fetch_assoc($result))
{
    echo '
    <div class="geoRow">
        <div class="flag"><img src="who-is-online/img/famfamfam-countryflags/'.strtolower($row['countryCode']).'.gif" width="16" height="11" /></div>
        <div class="country" title="'.htmlspecialchars($row['country']).'">'.$row['country'].'</div>
        <div class="people">'.$row['total'].'</div>
    </div>
    ';
}

Geodata.php is fetched by jQuery to populate the slide-out panel with location data. This file basically queues the database with a GROUP BY query, which groups the individual users by country and orders the resulting rows in a descending order, with the most popular countries at the top.

For the flag icons, we are using the famfamfam flag icon set, which is released as public domain. A great thing about the Hostip API, is that it returns the country code in a standard two letter format, which is also shared by the famfamfam icon set. This means that in the while loop, it is really easy to find the appropriate flag to show, by just lowering the case of the country abbreviation stored in the database and adding a gif extension.

Step 5 - jQuery

JavaScript manages the AJAX requests and slides the panel. This would be a daunting task with pure JS alone, which is why we are using the newest version of the jQuery library.

Now lets take a look at what the code looks like.

who-is-online/widget.js

$(document).ready(function(){
    // This function is executed once the document is loaded

    // Caching the jQuery selectors:
    var count = $('.onlineWidget .count');
    var panel = $('.onlineWidget .panel');
    var timeout;

    // Loading the number of users online into the count div with the load AJAX method:
    count.load('who-is-online/online.php');

    $('.onlineWidget').hover(
        function(){
            // Setting a custom 'open' event on the sliding panel:

            clearTimeout(timeout);
            timeout = setTimeout(function(){panel.trigger('open');},500);
        },
        function(){
            // Custom 'close' event:

            clearTimeout(timeout);
            timeout = setTimeout(function(){panel.trigger('close');},500);
        }
    );

    var loaded=false;   // A flag which prevents multiple AJAX calls to geodata.php;

    // Binding functions to custom events:

    panel.bind('open',function(){
        panel.slideDown(function(){
            if(!loaded)
            {
                // Loading the countries and the flags
                // once the sliding panel is shown:

                panel.load('who-is-online/geodata.php');
                loaded=true;
            }
        });
    }).bind('close',function(){
        panel.slideUp();
    });

});

You might be a bit perplexed with the use of setTimeout in the menu. This is done, so we have a bit of delay between the hovering of the mouse and the actual opening of the slide-out panel. This way, unintentional movements of the mouse cursor over the widget won't fire the open event, and once opened, will not close it immediately once the mouse leaves it.

With this our widget is ready!

Setting up the demo

At this point you probably want to grab the widget and put it on your site. To make it work, you need to execute the SQL code found in table.sql in the download archive. It will create the tz_who_is_online table in your database, which is used by the widget. Later you need to upload the files to your server and include widget.js to the head section of your page (along with the jQuery library). After this you have to fill your MySQL login details in connect.php and finally add the markup from demo.html to your web page.

Conclusion

Having access to real time data on your site userbase is a dream to any webmaster. Tools like Google Analytics give a great perspective on your site's reach, but lack the real time feel a simple widget like this can provide.

What do you think? How would you modify this code?

Bootstrap Studio

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

Learn more

Related Articles