Cómo cargar JavaScript

Como ya he comentado alguna otra vez, el JavaScript es uno de los elementos que bloquean la carga de los sitios web. Para evitar este bloqueo podemos usar algunos métodos creados con otro código de JavaScript que nos servirá para cualquier fichero externo que queramos cargar.

Lo bueno de estos sistemas es que permiten cargar en el sistema no sólo JavaScript sino que se podría abrir hasta CSS. Los códigos son bastante sencillos:

function loadScript(url, callback){
  var script = document.createElement("script")
  script.type = "text/javascript";
  if (script.readyState){ // Internet Explorer
    script.onreadystatechange = function(){
      if (script.readyState == "loaded" || script.readyState == "complete") {
        script.onreadystatechange = null;
        callback();
      }
    };
  } else { // Otros navegadores
    script.onload = function(){
    callback();
  };
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

Con esta función podemos cargar un script dentro del elemento head. Y con lo siguiente hacemos la llamada desde el código en el momento que queramos:

<script type="text/javascript" src="http://www.loquesea.ext/funcion.js"></script>
<script type="text/javascript">
loadScript("http://www.loquesea.ext/fichero1.js", function(){ });
loadScript("http://www.loquesea.ext/fichero2.js", function(){ });
</script>

Combinar y reducir JavaScript

En muchas ocasiones me encuentro que tengo varios JavaScript en una página y, al final, se hace bastante pesado tener que gestionar múltiples ficheros. Además, otra cosa que me gusta es la de reducir al máximo el tamaño del fichero, y el hecho de poder combinarlos también permite reducirlos…

Es por esto que existe para PHP una pequeña biblioteca de funciones llamada JSmin-php que ayuda a gestionar esta situación tanto la de combinar como de minimizar.

Básicamente lo que hace esta biblioteca es leer todos los ficheros JS de una carpeta, combinarlos, comprimirlos y generar un fichero único cacheado.

require_once("jsmin.php");
$files = glob("/carpeta/js/*.js");
$js = "";
foreach($files as $file) {
  $js .= JSMin::minify(file_get_contents($file));
}
file_put_contents("/carpeta/combinado.js", $js);

En el caso en que no queramos leer todos los ficheros de una carpeta y sólo incluior unos pocos, podemos cambiar la línea por algo al que así:

$files = array("/carpeta/js/1.js", "/carpeta/js/2.js", "/carpeta/js/3.js");

De esta forma generaremos un único fichero. Personalmente creo que esto habría que ejecutarlo una vez de forma externa, y usar el fichero generado a mano… a menos que montemos un sistema que verifique las cachés o que permita cambiar este fichero sin que ocurra nada… que luego ya se sabe, cambios de versiones, etc.

evercookie, la cookie que nunca desaparece

Una de las cosas que en algunos proyectos pueden ser interesantes es saber quién visita la página sí o sí. Normalmente usamos las cookies del navegador, con las opciones de que sean de sesión, de darles una fecha final más o menos cercana o lejana y, por supuesto, la opción de eliminarlas simplemente pulsando un par de botones en las opciones de nuestro navegador.

Pero seguro que en alguna ocasión has necesitado poner una cookie que no desaparezca ni aunque se vacíen todas las opciones de configuración. Si esto es lo que quieres, tu respuesta tiene nombre: evercookie.

El objetivo del proyecto evercookie es conseguir crear una cookie que sea prácticamente indestructible, y para ello utiliza algunas cosas como:

  • Standard HTTP Cookies
  • Local Shared Objects (Flash Cookies)
  • Storing cookies in RGB values of auto-generated, force-cached
  • PNGs using HTML5 Canvas tag to read pixels (cookies) back out
  • Storing cookies in and reading out Web History
  • Storing cookies in HTTP ETags
  • Internet Explorer userData storage
  • HTML5 Session Storage
  • HTML5 Local Storage
  • HTML5 Global Storage
  • HTML5 Database Storage via SQLite

evercookie está escrito en JavaScript, aunque usa elementos de SWF (para guardar en Flash) o PHP u otras herramientas que tenga disponible el sistema con tal de guardar esa información para que se vuelva indestructible. En principio, el único navegador que es capaz de no cargar este sistema es Safari en modo privado…

En algunos casos, esta cookie puede ser instalada desde un único navegador, pero utilizada desde algún otro, ya que hay algunos sistemas compartidos, sobre todo a la hora de guardar información general en la máquina.

Para descargarlo puedes visitar la página GitHub de evercookie o descargar la versión 0.3. Su uso es tan sencillo como:

<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="swfobject-2.2.min.js"></script>
<script type="text/javascript" src="evercookie.js"></script>
<script>
  var ec = new evercookie();
  // set a cookie "id" to "12345"
  // usage: ec.set(key, value)
  ec.set("id", "12345");
  // retrieve a cookie called "id" (simply)
  ec.get("id", function(value) { alert("Cookie value is " + value) });
  // or use a more advanced callback function for getting our cookie
  // the cookie value is the first param
  // an object containing the different storage methods
  // and returned cookie values is the second parameter
  function getCookie(best_candidate, all_candidates) {
    alert("The retrieved cookie is: " + best_candidate + "\n" +
    "You can see what each storage mechanism returned " +
    "by looping through the all_candidates object.");
    for (var item in all_candidates)
      document.write("Storage mechanism " + item + " returned: " + all_candidates[item] + "
");
  }
  ec.get("id", getCookie);
</script>

En fin… no sé si algún día llegaré a utilizarlo en alguno de mis proyectos, pero la verdad es que parece muy interesante para determinadas cosas… ya que la información y funcionalidad es impresionante.

Crear un efecto “desbloquear” de iPhone

Seguro que en alguna ocasión te gustaría hacer una versión de tu web para dispositivos móviles y añadir un toque iPhone. Es por esto que Chris Coyier ha creado un efecto “slide to unlock” en el que podemos apreciar, sobretodo en motores WebKit, el efecto a todo rendimiento.

Entre otras cosas, hay ese efecto degradado y gracias a jQuery también la posibilidad de hacer ese efecto slide.

El sistema aprovecha códigos CSS como el -webkit-animation: slidetounlock 5s infinite; y la función -webkit-keyframes slidetounlock.

El ejemplo funciona en motores Webkit al 100% (Safari y Chrome), aunque se pueden analizar los códigos del CSS y JavaScript.

Código CSS


* { margin: 0; padding: 0; }
html { background: black; }
body {
  font: 14px Georgia, serif;
  background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #3b3b3b),color-stop(1, #000000));
  background-repeat: no-repeat;
  min-height: 350px;
}
#page-wrap { width: 720px; margin: 0 auto; padding-top: 100px; }
#well {
  padding: 14px 20px 20px 20px;
  -webkit-border-radius: 30px;
  background: -webkit-gradient(linear,left top,left bottom,color-stop(0, #010101),color-stop(1, #181818));
  border: 2px solid #454545;
  overflow: hidden;
}
h2 {
  font-size: 80px;
  background: -webkit-gradient(linear,left top,right top,color-stop(0, #4d4d4d),color-stop(0.4, #4d4d4d),color-stop(0.5, white),color-stop(0.6, #4d4d4d),color-stop(1, #4d4d4d));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  -webkit-animation: slidetounlock 5s infinite;
  font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
  font-weight: 300;
  padding: 0;
  width: 200%;
}
h2 img {
  vertical-align: middle;
}
@-webkit-keyframes slidetounlock {
  0% {
    background-position: -720px 0;
  }
  100%{
    background-position: 720px 0;
  }
}

Código JavaScript


$(function() {
  $("h2 img").draggable({
    axis: 'x',
    containment: 'parent',
    drag: function(event, ui) {
      if (ui.position.left > 550) {
        $("#well").fadeOut();
      }
    },
    stop: function(event, ui) {
      if (ui.position.left < 551) {
        $(this).animate({
          left: 0
        })
      }
    }
  });
});

Reproductor HTML 5 para vídeo

Estamos muy mal acostumbrados a que nuestros vídeos se alojen en FLV en sitios como Youtube o Vimeo. Hoy en día teniendo en cuenta que el ancho de banda está tirado de precio, no entiendo porqué no se tiende a la calidad y a alojar la propia información si que otros decidan.

Con HTML 5 y su nuevo elemento <video> tenemos la posibilidad de incluir muchos formatos de vídeo con muchas opciones… y para hacerlo ya todo más sencillo, además de hacerlo compatible con versiones anteriores que no soportan los nuevos formatos, están apareciendo algunos sistemas que permiten estos reproductores de una forma “chula” y fácil.

Algunos de los que he encontrado son estos:

  • FlareVideo: Necesita de jQuery y el soporte es, a mi gusto, un poco reducido… hay que programar expresamente para ello.
  • MediaElementsJS: También necesita de jQuery, soporta todos los navegadores e incluso terminales móviles. Hay que incluir el nuevo tag <video> y unas pocas líneas más… Esto significa que cuando todos los navegadores den soporte, se quita y todo seguirá funcionando.
  • JW Player: La nueva versión de este tan utilizado reproductor ya es compatible con HTML 5. Necesita de jQuery y aun está en βeta.
  • VideoJS: No necesita de librerías externas y funciona gracias al elemento <video>, lo que, en principio, a mi me da mucha seguridad. Funciona con todos los navegadores y soporta los nuevos formatos.
  • SublimeVideo: Aunque aun no ha salido oficialmente, lo que se puede ver es parecido al VideoJS, es decir, que no necesita de librerías externas y funciona gracias al elemento <video>. En principio funciona con todos los navegadores, soporta la mayoría de formatos.

DISTCHA, un antispam accesible

Una de las peculiaridades de los CAPTCHA es que habitualmente son bastante complejos de entender y ya no os digo si cuesta a alguien con todas sus facultades, a alguien que tenga falta parcial o total de alguno de sus sentidos.

Y es por eso que desde Francia Quebec (Canada) llega una propuesta llamada DISTCHA (Device Independent Slider Test to tell Computers and Humans Apart) y que es un sistema bastante parecido al de activación del iPhone, ese “slide” que moviéndolo de un lado a otro permite activar el teléfono.

Aunque aun se encuentra en una versión muy previa, y a falta de ser probado en muchas plataformas, sí que es cierto que en la mayor parte de los navegadores ya funciona.

Funciona gracias a jQuery y es bastante sencillo de implementar. Os dejo un ejemplo de código para que veáis cómo funciona y uséis su código si lo veis conveniente…

HTML 5: el gran hermano te geolocaliza

Aunque se asocia el lanzamiento de las funciones de geolocalización al HTML 5, lo cierto es que la Geolocation API son unas funciones en JavaScript independientes de esta forma de mostrar las páginas web.

Básicamente el navegador, dependiendo de la conexión a Internet, será capaz de saber qué latitud, longitud y otros datos tienes en ese momento. Os dejo con un par de ejemplos de código que, dependiendo del navegador, deberían funcionar. El primero de ellos es simplemente textual, el segundo muestra un mapa.

El código más sencillo tiene una pinta tal que esta:

<script type="text/javascript">
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(successFunction, errorFunction);
  } else {
    alert('Tu navegador no soporta la geolocalizacion.');
  }
  function successFunction(position) {
    var lat = position.coords.latitude;
    var long = position.coords.longitude;
    alert('Tu localizacion es -- latitud : '+lat+' longitud : '+long);
  }
  function errorFunction(position) {
    alert('No se ha podido recuperar la geolocalizacion.');
  }
</script>

Open Standard Media (OSM) Player

Uno de los poyos que siempre me he encontrado a la hora de poner un reproductor de vídeo en la web es que en la mayoría de casos sólo aceptaba vídeos flash (.flv). La cosa es que con el HTML 5 y con jQuery se han montado un reproductor llamado Open Standard Media que tiene muy buena pinta, es código abierto y gratuito.

Entre otras cosas, permite el uso de HTML 5, soporta los nuevos elementos audio y video del HTML 5 con los formatos estándar, para el resto de formatos monta un reproductor Flash, se le puede cambiar el diseño de una forma sencilla gracias al uso de ThemeRoller, permite la integraciónd e vídeos de Vimeo y Youtube (simplemente indicando la URL), tiene la opción de listados de vídeos.

La instalación es muy sencilla, ya que sólo hay que añadir un par de JavaScript en la cabecera, poner el código básico del reproductor, y al final de la página, puedes incorporar un listado de los vídeos o una llamada a un XML que los tenga. Aun así, creo que la opción más sencilla es la de usar la librería en PHP, que con unas pocas líneas funcionaría:

<?php
include("OSMPlayer.php");
$player = new OSMPlayer(array(
  'file' => 'http://www.mysite.com/files/myvideo.flv',
  'image' => 'http://www.mysite.com/files/myimage.jpg',
  'disablePlaylist' => true
));
?>
<html>
  <head>
    <title>Open Standard Media (OSM) Player: PHP Demo</title>
    <script type="text/javascript" src="jquery-ui/js/jquery.js"></script>
<?php
print $player->getHeader();
?>
  </head>
  <body>
    <h2>Open Standard Media (OSM) Player</h2><br/>
<?php
print $player->getPlayer();
?>
  </body>
</html>

Con esto tendríamos un reproductor sencillo, aunque también se puede complicar un poco si le añadimos una lista de reproducción que nos permita visualizar decenas de vídeos… o si queremos modificar algunos parámetros, aquí está la lista de todos los disponibles…

Formas de validar un correo electrónico

Una de las cosas que habitualmente es necesario hacer es validar cuentas de correo. Hay muchas cosas a hacer, pero una de las primeras es saber si la cuenta de correo está “bien formada”. Un artículo muy interesante sobre distintas formas de verificar cuentas, basadas en una serie de expresiones que sí han de validar, y otras que no han de hacerlo.

La expresión que en principio se ajusta más a todas las posibilidades es la siguiente:

/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i

Con esto tendríamos un código similar a este:
<?php
$texto = "prueba@ejemplo.com";
$expresion = "/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i";
preg_match($expresion , $texto , $encuentros);
print_r($encuentros);
?>

De todas formas, quizá habría que hacer otra cosa, que es verificar si realmente la cuenta existe… y es que una cosa es que la cuenta de correo esté bien formada y otra es que pueda hacer una prueba de envío de correo sin necesidad de enviar. Para eso podríamos usar una clase como esta que se conecta a la máquina remota para verificar la existencia de la cuenta…

<?php
class SMTP_validateEmail {
  var $sock;
  var $user;
  var $domain;
  var $domains;
  var $port = 25;
  var $max_conn_time = 30;
  var $max_read_time = 5;
  var $from_user = 'pruebas@ejemplo.com';
  var $from_domain = 'localhost';
  var $nameservers = array(
    '192.168.0.1'
  );
  var $debug = false;
  function SMTP_validateEmail($emails = false, $sender = false) {
    if ($emails) {
      $this->setEmails($emails);
    }
    if ($sender) {
      $this->setSenderEmail($sender);
    }
  }
  function _parseEmail($email) {
    $parts = explode('@', $email);
    $domain = array_pop($parts);
    $user= implode('@', $parts);
    return array($user, $domain);
  }
  function setEmails($emails) {
    foreach($emails as $email) {
      list($user, $domain) = $this->_parseEmail($email);
      if (!isset($this->domains[$domain])) {
        $this->domains[$domain] = array();
      }
      $this->domains[$domain][] = $user;
    }
  }
  function setSenderEmail($email) {
    $parts = $this->_parseEmail($email);
    $this->from_user = $parts[0];
    $this->from_domain = $parts[1];
  }
  function validate($emails = false, $sender = false) {
    $results = array();
    if ($emails) {
      $this->setEmails($emails);
    }
    if ($sender) {
      $this->setSenderEmail($sender);
    }
    foreach($this->domains as $domain=>$users) {
      $mxs = array();
      list($hosts, $mxweights) = $this->queryMX($domain);
      for($n=0; $n < count($hosts); $n++) {
        $mxs[$hosts[$n]] = $mxweights[$n];
      }
      asort($mxs);
      array_push($mxs, $this->domain);
      $this->debug(print_r($mxs, 1));
      $timeout = $this->max_conn_time/count($hosts);
      while(list($host) = each($mxs)) {
        $this->debug("try $host:$this->port\n");
        if ($this->sock = fsockopen($host, $this->port, $errno, $errstr, (float) $timeout)) {
          stream_set_timeout($this->sock, $this->max_read_time);
          break;
        }
      }
      if ($this->sock) {
        $reply = fread($this->sock, 2082);
        $this->debug("< <<\n$reply");
        preg_match('/^([0-9]{3}) /ims', $reply, $matches);
        $code = isset($matches[1]) ? $matches[1] : '';
        if($code != '220') {
          foreach($users as $user) {
            $results[$user.'@'.$domain] = false;
          }
          continue;
        }
        $this->send("HELO ".$this->from_domain);
        $this->send("MAIL FROM: < ".$this->from_user.'@'.$this->from_domain.">");
        foreach($users as $user) {
          $reply = $this->send("RCPT TO: < ".$user.'@'.$domain.">");
          preg_match('/^([0-9]{3}) /ims', $reply, $matches);
          $code = isset($matches[1]) ? $matches[1] : '';
          if ($code == '250') {
            $results[$user.'@'.$domain] = true;
          } elseif ($code == '451' || $code == '452') {
            $results[$user.'@'.$domain] = true;
          } else {
            $results[$user.'@'.$domain] = false;
          }
        }
        $this->send("quit");
        fclose($this->sock);
      }
    }
    return $results;
  }
  function send($msg) {
    fwrite($this->sock, $msg."\r\n");
    $reply = fread($this->sock, 2082);
    $this->debug(">>>\n$msg\n");
    $this->debug("< <<\n$reply");
    return $reply;
  }
  function queryMX($domain) {
    $hosts = array();
    $mxweights = array();
    if (function_exists('getmxrr')) {
      getmxrr($domain, $hosts, $mxweights);
    } else {
      require_once 'Net/DNS.php'; // http://pear.php.net/package/Net_DNS/
      $resolver = new Net_DNS_Resolver();
      $resolver->debug = $this->debug;
      $resolver->nameservers = $this->nameservers;
      $resp = $resolver->query($domain, 'MX');
      if ($resp) {
        foreach($resp->answer as $answer) {
          $hosts[] = $answer->exchange;
          $mxweights[] = $answer->preference;
        }
      }
    }
    return array($hosts, $mxweights);
  }
  function microtime_float() {
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
  }
  function debug($str) {
    if ($this->debug) {
      echo htmlentities($str);
    }
  }
}
?>

Para qué navegador he de programar

En muchas ocasiones escucho una grandiosa frase del estilo: es que eso que me dices no funciona en Internet Explorer 6. A parte de que Explorer 6 es un producto caducado, y no lo digo yo, lo dice la propia Microsoft con una página sobre el upgrade a Explorer 8 que me parece especialmente divertida por la imagen que ofrece (verdad que no beberías leche caducada hace 9 años?), creo que vale la pena hacer cosas para cumplir el Principio de Pareto, también conocido como la regla del 80-20.

¿Esto que significa? Pues que, como en muchas ocasiones, Yahoo! dispone de una pequeña gráfica que viene a explicar qué navegadores son los que hay que utilizar, y lo lanza una vez por quarter (cada 4 meses). En este caso voy a hacer referencia a la que está en el momento de escribir esta entrada, que es el 1Q2010.

Windows XPWindows 7Mac OS 10.5Mac OS 10.6
x
x
xx
xxx
xx

¿Qué interpretación se puede hacer de esta tabla? Por un lado, que para determinadas versiones antiguas hay que simplemente obviar: Firefox < 3, Explorer < 7, Chrome < 4 y Safari < 4. Opera queda excluido por ser un navegador raro, como lo llaman ellos. Yo en estos casos lanzaría un mensaje de aviso de que tu navegador está caducado, actualiza gratis….

¿Qué otras más cosas? Pues que los navegadores nuevos pueden no estar preparados para ser 100% compatibles en los sistemas operativos, como pasa con Chrome.

En resumen: ¿para qué hay que desarrollar? Hay que desarrollar para Explorer 7 y 8 además de Firefox 3.6 (si hablamos de Windows) y para Safari 4 (si hablamos de Mac OS). Aunque no venga, yo para Linux dejaría el uso de Firefox 3.6 igualmente.

Con esto llegaremos a más del 80% de la población de Internet, con un trabajo simple, sin necesidad de dedicarle tiempo a la mayoría de tonterías que pueden dar los navegadores “raros” (entre comillas).

Otra cosa, como añadido… en un proyecto que tenemos actualmente en marcha, hemos puesto un mensaje de aviso a los usuarios de Explorer 5 y 6, y, la gente SÍ que actualiza su navegador, ya que desde que hemos puesto el mensaje, el 80% de las consultas para soporte que llegaban sobre “que la web no se veía bien”, han pasado a ser un 20% de las quejas… y si le sumamos a que según la analítica sólo tenemos un 10% de usuarios de Explorer < 7…

En definitiva, que cada uno haga lo que quiera con sus sitios, pero yo hace ya un tiempo que me he cansado de dar soporte y trabajar extra por culpa de un 10% de los usuarios de Internet que no se preocupan de su seguridad… así que si ellos no se preocupan por navegar correctamente, yo tampoco me preocupo por gente que no quiere preocuparse… y que seguramente luego me va a hacer perder tiempo.

Actualización: para los que me habéis preguntado, así se ve TuManitas en Explorer 6…