Web development , php , ajax , symfony, framework, zend
In: tutorial
10 Mar 2010For 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.
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.
<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.
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.
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).
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.
.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.
.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.
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.
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.
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.
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.
$(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!
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.
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?
This blog delivers stylish and dynamic news for designers and web-developers on all subjects of design, ranging from: CSS, Ajax, Javascript, web design, graphics, typography, advertising & much more. Our goal is to help you communicate effectively on the web with an engaging website or functional interface.
5 Responses to “Who Is Online” Widget With PHP, MySQL & jQuery
benaperez2
March 21st, 2010 at 11:29 am
you can try to use photobucket, but the codes probably too long
Use the link that says "Direct Link" & put it in this
<img src=IMGURLHERE>
if not just use tinypic.com
& copy the one that says "direct link for layouts"
Jack Mayoffer
March 24th, 2010 at 8:02 am
brandon jacobs is a beast…did u see him running over ppl tryin to tackle him on saturday against the ravens??
plus i'm a giants fan…so yea…
billrussell42
March 27th, 2010 at 2:01 pm
From L to R, top to bottom
a 6 volt battery
A LDR, light dependent resistor
10k resistor
FET, part number listed
diode
electrolytic capacitor
10M resistor
33 ohm resistor
LED
FET, part number listed
.
mishii318
April 9th, 2010 at 8:26 pm
I like Scream My Name better..the other beat is annoying. I hope you have a banging voice, because the Scream My Name beat is a little bland.
CarlosOctavio
May 19th, 2010 at 5:17 am
Bill is right, but you might want to note that direct-linking to other people's images is usually considered rude.