23
2011
Creating your own EE2 statistics gathering plugin using jQuery
Yet another statistics tool?
Every website owner loves gathering statistics. Always nice to see that your amount of weekly visitors went up from 10 to a staggering 12, right? That is of course why it’s pretty rare these days to find a website that doesn’t rely on some statistics tool like for example (the very popular) Google Analytics. While it might be a massive undertaking to actually build yourself something like Google’s service, it is not that hard to gather your own statistics using a very simple plugin and some jQuery magic. You can even do some neat and unique things that will most likely impress (or enrage) your website’s visitors and that aren’t (to my knowledge) possible with third-party services.
This little tutorial explains how to build your own EE2 plugin which will allow you to gather all sorts of statistics on a user, granted the fact that they have JavaScript enabled in their browser. But since services like Google Analytics also completely rely on JavaScript, I don’t think that it is going to be much of a problem.
What do we need?
An installation of Expression Engine 2 and jQuery. Just make sure you have the jQuery package included in the pages that you want to be able to gather statistics for. Including some HTML markup like:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js" /></script>
will do the trick if you don’t care about hosting the JavaScript libraries on your own server. Oh yeah, an installation of PhpMyAdmin or some other database management tool might come in handy.
A necessary warning about neglected coding standards
In this tutorial I’m showing you how to create a plugin but due to the fact that I want to store some data in a database table, we’ll need some SQL queries to create or drop a table to be executed. Just so you know: this is pretty bad practice. If you’re creating database tables, you should always consider developing a module instead of a plugin, because - amongst other reasons - modules have install and uninstall functionality that allow for table generation, something which isn’t (and probably shouldn’t be) available for plugins.
Plugins should only use existing tables and not create new ones. But for this tutorial, I’m going to be a filthy sinner and create a database table inside the plugin anyway. Bad, bad me. I hope you can forgive me. Now let’s have a look at the actual code:
The plugin’s code
<?php
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$plugin_info = array(
'pi_name' => 'jStats',
'pi_version' => '1.0',
'pi_author' => 'Michiel Papenhove',
'pi_author_url' => 'http://pinktutu.biz/',
'pi_description' => 'Uses jQuery to fetch and store visitor statistics.',
'pi_usage' => Jstats::usage()
);
class Jstats
{
var $return_data;
var $parameters = array();
function Jstats()
{
$this->EE =& get_instance();
}
function install()
{
$this->EE->load->dbforge();
$fields = array(
'log_id' => array('type' => 'bigint', 'auto_increment' => TRUE),
'log_action' => array('type' => 'varchar', 'constraint' => 255),
'log_data' => array('type' => 'varchar', 'constraint' => 255),
'ip_address' => array('type' => 'varchar', 'constraint' => 25),
'site_path' => array('type' => 'varchar', 'constraint' => 255),
'log_date' => array('type' => 'datetime')
);
$this->EE->dbforge->add_field($fields);
$this->EE->dbforge->add_key('log_id', TRUE);
$this->EE->dbforge->add_key('ip_address');
$this->EE->dbforge->create_table('jstats_log', TRUE);
return "Tables were created.";
}
function uninstall()
{
$this->dbforge->drop_table('jstats_log');
}
function ajaxbridge()
{
$ip_address = $this->EE->input->ip_address();
$log_action = trim($this->EE->db->escape_str($this->EE->input->post('log_action')));
$msg = trim($this->EE->db->escape_str($this->EE->input->post('message')));
$site_path = $this->EE->db->escape_str($this->EE->input->server('HTTP_REFERER'));
switch($log_action)
{
case "click": $action = 'External link clicked'; $action_data = $msg; break;
case "page_loaded": $action = 'Page loaded'; $action_data = ''; break;
case "mouseover": $action = 'Mouse over'; $action_data = $msg; break;
default: die('Invalid action "'.$log_action.'"');
}
$record = array('log_action' => $action, 'log_data' => $msg, 'ip_address' => $ip_address, 'log_date' => date('Y-m-d H:i:s'), 'site_path' => $site_path);
$this->EE->db->insert('jstats_log', $record);
return json_encode(array('result' => 'OK'));
}
function track()
{
$ajax_path = $this->EE->TMPL->fetch_param('ajax_path');
if (!$ajax_path) die('Please set the "ajax_path" parameter');
return <<<EOT
<script type="text/javascript">
function log(action, msg)
{
var params = { log_action:action, message:msg };
$.post('{$ajax_path}', params, false, "json");
}
$(document).ready(function()
{
log('page_loaded', '');
$('a').click(function(e) { log('click', e.target); } );
$('img').mouseover(function(e) { log('mouseover', e.currentTarget.src); } );
});
</script>
EOT;
}
function usage()
{
ob_start();
?>
Our own little statistics gathering plugin.
<?php
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
}
To get this working, create a file named pi.jstats.php and copy/paste in the code listed above, then put it in a directory named jstats and finally place or FTP the newly created directory inside your site’s /system/expressionengine/third_party/ folder.
The plugin starts off with some stuff that is required by Expression Engine. You’re not supposed to be able to access plugins directly so there’s a BASEPATH check to make sure the plugin was included/required by some other class or script. After this is done, an array is created that EE uses to format (amongst others) the plugin overview screen in the Control Panel.
What follows is the actual class or plugin definition. First of all, there’s the Jstats() constructor method that gets an instance of Expression Engine to be able to use its functionality. So far so good. Then it gets kind of nasty.
The two methods (install and uninstall) that follow are used to - not suprisingly - install and uninstall the plugin. I think I’ve expressed quite clearly that this is not good practice, due to the fact that plugins usually do not create their own database tables. But creating a “real” module (instead of of plugin) for EE2 requires the use of several scripts and directories, something I wanted to avoid in this tutorial. That’s the reason I maliciously ignored good Expression Engine coding practices. Again, I hope you can forgive me.
To install the plugin (basically this just creates a table named jstats_log to store log messages, it doesn’t modify any other EE tables which does happen when you install an actual module) you just create or re-use an EE template. Put the following code in it and surf to the template on your site:
{exp:jstats:install}
The table will now be created and a confirmation message will appear. That’s it. You can remove the template (or just the line if you re-used a template). If you ever want to drop the table and don’t feel like using a tool like PhpMyAdmin, put this code in a template:
{exp:jstats:uninstall}
And load the template by surfing to it in your preferred browser. Bang. Gone with the wind.
On to the more exciting stuff
Continuing our review of the plugin’s code; the next method is called ajaxbridge and the name explains exactly what it does: it functions as an AJAX “bridge” between the user’s browser and the web server. You will need to create another template to allow the bridge to do its work. Create a new template (mine’s /jstats/) and put the following code in it:
{exp:jstats:ajaxbridge}
If you have a look at the ajaxbridge method, you’ll see that it requires two variables to be POST-ed to it. One of these variables is named log_action, the other one’s called message. Another variable called site_path is set to contain the value of the server’s HTTP_REFERER. This variable will contain the URI to the web page from which the request was initiated.
Because I wanted this tutorial to be fairly basic, I just used a simple switch to determine what to log to the statistics table. There are way better ways of doing what I did there but for the sake of simplicity, I just hard-coded three possible log actions. Please, do feel free to turn this into something much nicer.
The three possible actions are click, which will be used to log a click on a link to an external (and popped in a new window, see the caveats section for more info) URL, the page_loaded action that will signal the initial load of a page and there’s an action for logging mouse over events on images in the user’s browser. When the values have been set, they are inserted into the jstats_log table. Each inserted record will then contain a log action, some optional data, the date and time of the logged action, the user’s IP-address and the URI of the page from which the request was initiated.
The method returns a JSON-object that will always contain a result variable with the value of OK, as long as you passed a valid log_action variable. Since our plugin is not going to communicate in a two-way fashion with the requesting web page, just returning this pretty lame array is fine for now.
The last method (not counting the obligatory usage() method that EE requires to show a plugin information screen) contains a piece of code that will generate some JavaScript to get us started. As I will discuss later on, you can go completely crazy with this piece of code. For this example however, we will keep things - as stated before - pretty basic.
The track() method checks if a template parameter called ajax_path is set. If this is not the case, the method just brutally dies with an error message. If you’re ever going to use something like I’m describing in this tutorial, you should obviously create a more streamlined way of communicating errors to end users. The ajax_path parameter is used to point to the AJAX bridge template you created earlier on in this article.
The method then generates some JavaScript code. If you are familiar with jQuery, this piece will probably be very easy to comprehend. For those of you who don’t really know jQuery or just got started with it, when you see something like $.post or $(‘a’), you’re looking at jQuery being asked to perform its magic. The $.post method is used to send a JSON object to a webpage, in this case our AJAX bridge. As you can see in the supplied code, this JSON object is made up of a log_action and a message key and value (remember those?). As soon as the JavaScript log() method is called, it will send the request to the AJAX bridge.
Let’s have us a closer look at the following piece of code:
$(document).ready(function()
{
log('page_loaded', '');
$('a').click(function(e) { log('click', e.target); } );
$('img').mouseover(function(e) { log('mouseover', e.currentTarget.src); } );
});
This is jQuery at work again. What happens here is that the document’s ready event is connected to a custom function. What this means is that as soon as the page has been loaded in a user’s browser, the little piece of code between the brackets is executed. When this occurs, the script immediately calls the log method which in turn sends a JSON object to the AJAX bridge. The message sent is ‘page_loaded’. which will be converted to something more human readable by our plugin. And there you have it, our first log entry. The two other lines possibly require a bit more explanation.
javascript’s dirty tricks
Please allow me to explain the header of this part of the tutorial: with the simple plugin provided above, you can do a lot of things. And I mean a lot of things. Frameworks like jQuery make it a breeze to - for example - have certain elements in your page listen for events like mouse-overs to happen. Or clicks. In this example I just listen for clicks on anchor tags and mouse-overs on images. Nothing that special. But please consider what might actually be possible.
How about listening for mouse-overs on all elements of your page and logging their occurrences? Or how about capturing a window’s scrolling value to determine if a user is actually still reading a page? Or a bit simpler, what about setting up a timer that sends checks to the server once a minute, allowing you to harvest accurate statistics on how long people stay on a page? Instead of just seeing so-called ‘click paths’ you can actually see what a user points at on pages you create. Or add a DIV-tag to your page’s bottom HTML so that an event will trigger if a user scrolls all this way; that is almost certainly someone who read the entire article. Et cetera, et cetera. The possibilities are quite limitless. And perhaps a bit frightening when seen from a privacy perspective. User navigation profiling was never easier.
I don’t want to spend to much time on fantasizing about other possibilities, I’ll leave that up to the reader. Please do consider that maniacally logging everything your users do might not be the best or nicest thing to do when you care about those little things like karma or possible future harassment lawsuits. Please don’t go 1984 on your visitors. Besides that, also keep in mind that firing a lot of small AJAX requests to your web server will most likely still give your server a pretty hard time (especially on high traffic sites) or in the worst case even a virtual cardiac arrest. If you have thousands of users visiting your site at the same time, you’re perhaps better off sticking to more “old-fashioned” tools like Google Analytics. Or get yourself a bigger server.
Having said all this, let’s get back to the code. You probably want to see it in action, which is pretty down to earth again. Just open a (sub) template you use as a header for your pages and add the following code:
{exp:jstats:track ajax_bridge="/jstats/"}
Now open up your website and load some pages. Then check the jstats_log database table to have a look at the results gathered. If you have any images on your pages, mouse over them to see this event also being logged. Click on links to external pages and you’ll see an entry popping up showing you exactly what link was clicked.
caveats
There are some things you need to consider when using a statistics gathering plugin as described above. First of all and as I mentioned earlier, everything relies on JavaScript. So if somebody turns JavaScript off or you have a search bot visiting your website, no log entries will show up. Secondly, you need to be aware of how certain things work in JavaScript and modern browsers. This especially goes for stuff like anchor tags and clicks on these elements.
It is possible to listen for a click event on an anchor, as I do in this line:
$('a').click(function(e) { log('click', e.target); } );
If you try this out for yourself you’ll probably notice the same result I noticed (note: I’m currently working with Firefox on Linux, so it might be slightly different in other browsers). If somebody clicks on a link that pops a new window, all is good. The plugin nicely registers the click and logs an entry to the database. However, if the link is not popped in a new window, nothing shows up. This happens because the click on a link then immediately replaces the current page with the fresh one, thereby unloading your jQuery and other JavaScript code.
There’s a way around this behavior, but things do start to feel kind of ‘hacky’ when you try to work around issues like the anchor problem. You have been warned in advance. JQuery allows you to use the preventDefault() method to suppress default behaviour but that does imply that you have to make up for it yourself. It would of course be pretty weird if somebody clicked on a link and you end up with a perfect and shiny log entry, but the user never actually gets anywhere because the link doesn’t work as suspected anymore. Bad. A possible workaround would be: to send the log request to the server and wait for a response. As soon as you get that response, you redirect the user to the URI he or she clicked on or you pop a new window pointing to the URI. Hacky indeed.
Lastly, this plugin just gathers data. It doesn’t do anything to nicely format it into pie graphs or other sorts of fancy eye candy. That is up to you, if you decide that you want to start using JavaScript to gather your own statistics. If you are thinking in this direction, I would advise you to turn this plugin into a module.
conclusion
I hope you enjoyed reading this tutorial as much as I enjoyed writing it. If you did, please let me know so I might do another one in the future. If you have any special suggestions (like for example turning this little plugin into a full-blown module with Control Panel interface), I’d love to hear that too. Of if you have any questions, feel free to comment or contact me. And of course, you can also follow me on twitter.
4558x viewed
your comments
No comments as of yet. Will you be the first?
Commenting is not available in this channel entry.
