Seguramente os habéis cruzado, al navegar por alguna página web, con el tÃpico widget que te indica quién está visitando la página en este justo instante. En páginas webs corporativas y tiendas online no es muy común ver componentes asÃ, pero si hablamos de un foro, la cosa cambia. Se trata de un elemento que muestra información sobre las personas que están conectadas a la página en el mismo momento que tú. En este sencillo tutorial te mostraremos cómo implementar el tÃpico “Quién está online” en tu sitio web. ¿Estás preparado? Pues vamos a ello.
El XHTML
Como es habitual, vamos a empezar con la parte del XHTML. El código que te presentamos aquà no es mucho, pero es todo lo que necesitamos para mostrar el trabajo que se hace desde el backend. El widget mostrará un panel deslizante con todos los datos de la geolocalización al pasar el ratón por encima.
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>
Como puedes ver en el código de arriba, el contenedor principal “onlineWidget” contiene el panel deslizante (el div con nombre de clase “panel”), el número total de gente online (el div “count”), el label “online” y la flecha verde a la derecha.
El div panel se completará dinámicamente con AJAX con los paÃses que más visitantes tienen online actualmente. El contenido por defecto de este div es un gif de cargando que rota, que es reemplazado con los datos de geolocalización una vez que la respuesta AJAX se haya completado (normalmente, en menos de un segundo). Volveremos a esto más adelante...
La base de datos
Vamos a echar un vistazo a cómo estructurar la base de datos para que se integre en nuestro proyecto, ya que es fundamental para el resto del script.
Todos los datos del widget se almacenan en la tabla tz_who_is_online. Consiste en seis campos (o columnas). El primer campo, ID, es la primary key de la tabla y además auto incremental. Después tenemos el campo IP, en el que guardaremos la IP del visitante (convertida en un entero con la función ip2long de PHP).
Después tenemos tres campos que se rellenarán con la API de Hostip: Country, CountryCode y City. El widget no utilizará el campo city por el momento, pero es bueno tenerlo en caso de que quieras implementarlo. Por último tenemos un campo DT llamado timestamp, que se actualiza en cada carga de página y nos habilita el trackear quién está online (los usuarios sin una página cargada en los últimos 10 minutos, seguramente habrán abandonado el sitio web).
El CSS
El widget está (casi) libre de imágenes, y solo se personaliza con CSS. Vamos a echarle un vistazo al estilo, definido en su hoja de estilos correspondiente. El código está dividido en dos partes para que sea más sencillo de seguir.
who-is-online/styles.css - Parte 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; }
En el código anterior, puedes ver que le hemos dado estilo al widget y al panel deslizante a la vez. Esto es para asegurarnos de que tienen un estilo consistente, y que sea sencillo de modificar más adelante. Algunas reglas son únicas para el panel, de todas maneras, incluimos un conjunto de reglas especÃficas en la segunda parte del código.
También definimos el hover y el estilo del label y del div count.
who-is-online/styles.css - Parte 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; }
En la segunda parte del código, damos estilo a los datos de geolocalización que se mostrarán en el panel deslizante, más tarde jQuery lo completará desde el backend. Con esto, podemos continuar al próximo paso.
El PHP
Aquà es donde ocurre la magia. PHP cuenta con la tarea de mantener la base de datos actualizada y obtener los datos IP de la API de Hostip. Más tarde se almacerá en cache para usarlos posteriormente en una cookie del ordenador visitante.
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;
Este script de PHP será llamado por jQuery con el fin de contabilizar el número actual de personas online y rellenar el div count. Resumiendo, este script inserta la IP del visitante en la base de datos y extrae los datos de su ubicación en base a ella.
La mejor estrategia para una mejor organización del back-end, es hacer las llamadas a la API (que tardan bastante) por cada usuario, cuando visiten el sitio por primera vez.
La otra alternativa serÃa la de almacenar sólo las direcciones IP de los visitantes y resolver los datos de geolocalización una vez que se muestren en el panel. Esto significarÃa resolver un gran número de direcciones IP de forma simultánea, lo que harÃa que el scrip se colapsase y entrarÃamos en la lista negra de la API. Esto no mola nada.
Puede resolver los datos de geolocalización con la API del HostIP abriendo una conexión en una URL similar a esta: http://api.hostip.info/?ip=128.128.128.128. Esto devolverá una respuesta XML válida, que contiene todo tipo de datos, incluyendo el nombre del paÃs y la ciudad asociadas con la IP, la abreviatura del paÃs e incluso las coordenadas absolutas. Vamos a extraer estos datos con la función file_get_contents() de PHP para guardar la información que necesitamos.
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> '; }
Llamamos a Geodata.php con jQuery para completar el panel deslizante con los datos de la geolocalización. Este archivo, básicamente, extrae la información con una consulta GROUP BY, que agrupa a los usuarios individuales por paÃs y ordena las filas resultantes en orden descendente, con los paÃses más populares en la parte superior.
Para los Ãconos, utilizamos el conjunto iconos de banderas famfamfam, que es de dominio público. Una gran carcaterÃstica de la API HostIP, es que devuelve el código del paÃs en un formato estándar de dos letras, que también es utilizado por el conjunto de iconos famfamfam. Esto significa que en el bucle while, es sencillo encontrar la bandera a mostrar, solo hay que poner la abreviatura del paÃs y añadir la extensión jpg.
El jQuery
Javascript gestiona las respuestas de AJAX y el panel deslizante. Esto serÃa una tarea de enormes proporciones mediante JS puro, por lo que vamos a utilizar la versión más reciente de la librerÃa jQuery.
El código serÃa el siguiente:
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(); }); });
Te habrás quedado un poco perplejo con el uso del setTimeout en el menú. Esto se hace para tener un poco de delay entre el hover del ratón y la apertura real del panel deslizante. De esta manera, los movimientos involuntarios del cursor del ratón sobre el widget no dispararán el evento open, y una vez abierto, no se cerrará de inmediato una vez que el ratón quite su posicionamiento del elemento.