Prototype is a Javascript framework that aims to ease development of dynamic web
applications. Among other things that this framework can offer, what
really interests us here is that the guys at Prototype have an
excellent and easy-to-use toolkit for developing Ajax applications.
Periodical visitor counter or live update with Ajax and Prototype
Introduction
What is Prototype?
Prototype is a Javascript framework that aims to ease development of dynamic web
applications. Among other things that this framework can offer, what
really interests us here is that the guys at Prototype have an
excellent and easy-to-use toolkit for developing Ajax applications.
Furthermore, we can elaborate on Prototype by using Scriptaculous
which adds even more functionality and some interesting special effects.
2.2. You will need:
- to download the Prototype Javascript framework.
- to
have a basic understanding of PHP/MySQL. As I will not explain the code
in elaborate detail. Since it's not the purpose of this guide.
3. Javascript for our Ajax periodical visitor counter
Before reading on. I should point out that I made this piece of javascript so we can reuse it.
This means, insert the updater.js in every file where you need to have some kind of live updating.
Then call the javascript by writing:
- <script type="text/javascript" src="updater.js"></script>
- <script type="text/javascript">
-
- document.observe('dom:loaded', function() {
-
-
-
-
-
- var visitorCounter = new updater('counter', 1, 'countVisitors.php');
-
-
- visitorCounter.getUpdate();
- });
-
- </script>
-
<script type="text/javascript" src="updater.js"></script>
<script type="text/javascript">
// <![CDATA[
document.observe('dom:loaded', function() {
/*
first arg : div to update
second arg : interval in seconds
third arg : file to get data from
*/
var visitorCounter = new updater('counter', 1, 'countVisitors.php');
// Make first call so we get an immediate update after the page is loaded
visitorCounter.getUpdate();
});
// ]]>
</script>
We'll explain the updater.js later on. For now remember that the file stands alone and can be used for all kinds of live polling.
save following snippet as updater.js
- var updater = Class.create({
- initialize: function(divToUpdate, interval, file) {
- this.divToUpdate = divToUpdate;
- this.interval = interval;
- this.file = file;
- new PeriodicalExecuter(this.getUpdate.bind(this), this.interval);
- },
-
- getUpdate: function() {
- var div = this.divToUpdate;
- var interval = this.interval;
- var file = this.file;
- var oOptions = {
- method: "POST",
- asynchronous: true,
- parameters: "intervalPeriod="+interval,
- onComplete: function (oXHR, Json) {
- $(div).innerHTML = oXHR.responseText;
- }
- };
- var oRequest = new Ajax.Updater(div, file, oOptions);
- }
- });
-
var updater = Class.create({
initialize: function(divToUpdate, interval, file) {
this.divToUpdate = divToUpdate;
this.interval = interval;
this.file = file;
new PeriodicalExecuter(this.getUpdate.bind(this), this.interval);
},
getUpdate: function() {
var div = this.divToUpdate;
var interval = this.interval;
var file = this.file;
var oOptions = {
method: "POST",
asynchronous: true,
parameters: "intervalPeriod="+interval,
onComplete: function (oXHR, Json) {
$(div).innerHTML = oXHR.responseText;
}
};
var oRequest = new Ajax.Updater(div, file, oOptions);
}
});
Alright folks. This is our core javascript class.
our class is called updater.
The very first method (a method is the name used for a function inside a class) is called initialize: function(). That's similar to the PHP5 initialiser method: __construct().
This means that this method is always called when the class is instantiated.
initialize: function()
You can see that the initialize method takes three arguments:
- divToUpdate: that is the div element we want to update, in our example: counter
- interval:
when the system should check for new visitors measured in seconds. In
our code I use 30 seconds: our back-end (the php code) will do a check
for new visitors every 30 seconds.
- file: the file we want to poll, that's our php file, which does the actual checking and gives us the output
We assign the arguments to global variables (within the class scope that is).
Next we instantiate the built-in (in Prototype) object called PeriodicalExecuter(), which in its turn takes 2 arguments:
- the method to execute
- the interval measured in seconds to execute function
getUpdate: function()
Next we define a method that we can name as we like (in our example that is getUpdate: function()).
In the first three lines var div = this.divToUpdate; var interval = this.interval; var file = this.file;
of this method we store the variables that were passed to us by our initialization method initialize: function(), which
is only called one time of course (when we load the page).
We store them so we can use them later on every time the getUpdate: function() is called.
The oOptions object (the little o stands for object, just a good coding convention) contains the following properties:
- method: "get" or "post" (update: IE6 seems to have problems with the GET method, use POST instead.)
- asynchronous:
which can be true or false and determines if the AJAX call to the
server will be made asynchronously (the default value is true.)
- parameters: the data to be sent to the URL. Typically, a URL-encoded string of name-value pairs. The combining of the URL
and the parameters
is handled by Prototype behind the scenes (it could look like this:
visitorCounter.php?intervalPeriod=30). The value of the intervalPeriod
is passed on to our back-end.
- onComplete: function to call when the call is complete. The code within updates our container div (this.divToUpdate) with the standard oXHR.responseText.
- onSuccess: function to call when the response has been successfully received
- onFailure: function to call when the response has failed
- onLoading: function to call when the response is loading. Not necessary I believe since we're updating very frequently. Seeing a
'loading' message every time can get quite annoying
The onComplete(), onSuccess() and onFailure() methods are functions that are passed 2 arguments: the XHR object used to make the request and an optional
JSON object with additional information about the request. Nothing to worry about in this tutorial.
new Ajax.Updater()
After we declared our options to pass along, we're going to send the request. This will be done with the following code: Ajax.Updater(element_id, url, options).
It takes an HTML element's ID as its first argument. So when a response
is received (our number of visitors in this case) Ajax.Updater puts the
response into the HTML element with the given ID we provided. We have
written new Ajax.Updater(this.divToUpdate, this.file, oOptions);
So the response will be displayed in the div element we passed along as an argument in the initialize: function();.
this.file is a variable where we stored our third argument of the initialize: function(); in.
Voilà, that's it. The logic is fairly simple. We call our method (it gets our data) and we execute that method every x seconds, where x is the
second argument in the initialize: function();.
PeriodicalExecuter VS PeriodicalUpater
For those wondering why I use PeriodicalExecuter instead of PeriodicalUpater:
PeriodicalUpdater has some problems. Memory leaks. I found my browser
grinding to a halt after about 600 requests with PeriodicalUpdater. The
leaks were fixed a while ago (check for the patch:
http://dev.rubyonrails.org/changeset/8152) For compatibility's sake we
could use the clearInterval / setInterval combo. A warm and fuzzy
solution. Instead I opted for PeriodicalExecuter. Documentation says:
"This is a simple facility for periodical execution of a function. This
essentially encapsulates the native clearInterval/setInterval mechanism
found in native Window objects." Clean and simple. Also fuzzy.
Special thanks goes out to Josef Wendel who pointed out a bug in my code.
The code in the example has been updated.
4. PHP for our periodical live update visitor counter
Fire up good ol' phpmyadmin and execute following sql code:
- CREATE TABLE `visits` ( `visitor_ip` int(10) NOT NULL default '0', `visitor_time` int(10) NOT NULL default '0', KEY `visitor_time` (`visitor_time`) ) TYPE=MyISAM;
CREATE TABLE `visits` ( `visitor_ip` int(10) NOT NULL default '0', `visitor_time` int(10) NOT NULL default '0', KEY `visitor_time` (`visitor_time`) ) TYPE=MyISAM;
save as countVisitors.php.
- <?php
- header('Content-Type: text/html; charset=UTF-8');
- header('Cache-Control: no-cache');
- header('Pragma: no-cache');
-
-
-
-
-
-
-
-
-
-
- $timeframe = $_POST['intervalPeriod'];
-
-
- $db_host = "localhost";
- $db_name = "your_db_name";
- $db_user = "your_username";
- $db_pass = "your_password";
- $db_table= "visits";
-
-
- $connection = mysql_connect($db_host, $db_user, $db_pass);
- mysql_select_db($db_name, $connection) or die("Error. Cannot connect to database");
-
-
- $visitor_ip = ip2long($_SERVER['REMOTE_ADDR']);
-
-
- $time = time();
-
-
- $new_visitor = true;
-
-
- $get_ip = mysql_query("SELECT * FROM " . $db_table . " WHERE visitor_ip=" . $visitor_ip . " LIMIT 1");
- while ($row = mysql_fetch_object($get_ip)) {
- mysql_query("UPDATE " . $db_table . " SET visitor_time=" . $time . " WHERE visitor_ip=" . $visitor_ip . "") or die(mysql_error());
- $new_visitor = 0;
- }
-
-
- if ($new_visitor === true) {
- mysql_query("INSERT INTO " . $db_table . " (visitor_ip, visitor_time) VALUES ('$visitor_ip','$time')") or die(mysql_error());
- }
-
-
- $tcheck = time() - $timeframe;
-
- $query = mysql_query("SELECT * FROM " . $db_table . " WHERE visitor_time > $tcheck");
- $onlinenow = mysql_num_rows($query);
-
-
- if($onlinenow == 1) {
- echo "currently $onlinenow visitor online";
- } else {
- echo "currently $onlinenow visitors online";
- }
- ?>
<?php
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-cache');
header('Pragma: no-cache');
// visitor counter
// by: Nico Beekhuijs
// date: 03-11-2006
// This script counts the number of visitors on the website
// for the last x minutes.
// Visitors are tracked by their IP address making several
// visitors from the same IP (ie a proxy) count as one.
// Tracking by IP is good enough in most cases
// config variables
$timeframe = $_POST['intervalPeriod']; // time frame (seconds) to count active users
// database connection details
$db_host = "localhost"; // hostname of your MySQL server. You most likely don't have to change this
$db_name = "your_db_name"; // database name
$db_user = "your_username"; // database user
$db_pass = "your_password"; // database password
$db_table= "visits"; // table name
// Lets open up a connection to the database
$connection = mysql_connect($db_host, $db_user, $db_pass);
mysql_select_db($db_name, $connection) or die("Error. Cannot connect to database");
// generates an IPv4 Internet network address from its Internet standard format (dotted string) representation.
$visitor_ip = ip2long($_SERVER['REMOTE_ADDR']);
// time() returns INT
$time = time();
// set Flag to TRUE, if we got a new visitor
$new_visitor = true;
// update database for returning visitor
$get_ip = mysql_query("SELECT * FROM " . $db_table . " WHERE visitor_ip=" . $visitor_ip . " LIMIT 1");
while ($row = mysql_fetch_object($get_ip)) {
mysql_query("UPDATE " . $db_table . " SET visitor_time=" . $time . " WHERE visitor_ip=" . $visitor_ip . "") or die(mysql_error());
$new_visitor = 0;
}
// add to database for new visitor
if ($new_visitor === true) {
mysql_query("INSERT INTO " . $db_table . " (visitor_ip, visitor_time) VALUES ('$visitor_ip','$time')") or die(mysql_error());
}
// done processing the visit, now lets see how many total visitors are online
$tcheck = time() - $timeframe; // (30 = 30 seconds)
// select visitors that visited our page the last 30 seconds.
$query = mysql_query("SELECT * FROM " . $db_table . " WHERE visitor_time > $tcheck");
$onlinenow = mysql_num_rows($query);
// show number of visitors on screen
if($onlinenow == 1) {
echo "currently $onlinenow visitor online";
} else {
echo "currently $onlinenow visitors online";
}
?>
5. HTML for our Ajax periodical visitor counter
save as index.html.
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl">
- <head>
- <title>Ajax with Prototype: Live update</title>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
- <script type="text/javascript" src="prototype.js"></script>
- <script type="text/javascript" src="updater.js"></script>
- <script type="text/javascript">
- // <![CDATA[
- document.observe('dom:loaded', function() {
- /*
- first arg : div to update
- second arg : interval to poll in seconds
- third arg : file to get data
- */
- var visitorCounter = new updater('counter', 1, 'countVisitors.php');
- visitorCounter.getUpdate();
- });
- // ]]>
- </script>
- </head>
- <body>
- <h2>Live update current visitor number</h2>
- <div id="counter">
-
- </div>
- </body>
- </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl">
<head>
<title>Ajax with Prototype: Live update</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="updater.js"></script>
<script type="text/javascript">
// <![CDATA[
document.observe('dom:loaded', function() {
/*
first arg : div to update
second arg : interval to poll in seconds
third arg : file to get data
*/
var visitorCounter = new updater('counter', 1, 'countVisitors.php');
visitorCounter.getUpdate();
});
// ]]>
</script>
</head>
<body>
<h2>Live update current visitor number</h2>
<div id="counter">
</div>
</body>
</html>
Two things.
1. We call our class by putting a new keyword before it. If you want multiple instances write (var livestock = new updater() and var liveguestbook = new updater()).
It takes 3 arguments:
- divToUpdate: that is the div element we want to update: in our example: counter
- interval: when the system should check for new visitors measured in seconds
- file: the file we want to poll, that's our php file, which does the actual checking and gives us the output
2. We make our container div <div id="counter"></div>, where the content can be placed in.
|