miércoles, 6 de marzo de 2013

Tutorial: como hacer tooltips con CSS

Un tooltip es esa ventanita de información que aparece cuando colocamos el ratón sobre algún elemento, como podemos ver en esta imagen de un botón de Gmail:


Se puede implementar de muchas maneras, aquí vamos a ver las 2 opciones más sencillas: con HTML y con CSS. Dejaremos para otra entrada la implementación con JavaScript.

HTML, tooltips básicos con el atributo title


En HTML el atributo title puede utilizarse para mostrar un tooltip básico. Es válido para casi todos los elementos ( en HTML5 ya se puede incluir en todos ):
<button title="This is a tooltip" type="button">Press</button>
El problema es que no admite ningún tipo de personalización. Sólo puede mostrarse texto simple, no podemos formatearlo mediante etiquetas HTML ni poner saltos de línea donde queramos.

Elementos de un tooltip

Los conceptos para crear un tooltip propio son sencillos, básicamente el mensaje no es más que un elemento HTML, normalmente un span o un div, con un texto informativo dentro.

<div class="tooltip">
This is a tooltip
</div>
Despues, mediante estilos CSS, le damos el aspecto que queramos y lo posicionamos donde corresponda con posicionamiento absoluto para que no desplace a otros elementos de la página al mostrarse:

.tooltip {
  position: absolute;
  padding: 10px;
  background-color: gray;
  color: white;
  top: 100%;
  left: 95%;
}

Jugando con los estilos podemos crear ejemplos espectaculares como estos, sacados del post [CSS3 tooltips de Catalin Rosu] (http://www.red-team-design.com/css3-tooltips):


Tooltips usando sólo CSS. El método tradicional

Para crear el mensaje utilizando sólo CSS vamos a incluir el texto de información en un span que inicialmente estará oculto ( display: none). Para que se muestre cuando colocamos el ratón encima (hover) definiremos un estilo diferente para la pseudo-clase :hover con display: block.
<button>Press
    <span class="tooltip">This is a tooltip</span>
</button>
El siguiente paso es definir el CSS para darle el aspecto que queramos, colocarlo en el lugar donde debe mostrarse y definirlo como oculto por defecto y visible sólo en el :hover .
button {
  position: relative;
}

.tooltip {
  position: absolute;
  display: none;
  border: 1px solid black;
  padding: 3px;
  background-color: gray;
  color: white;
  width: 100px;
  left: 100%;
  top: 100%;
}

button:hover .tooltip {
    display:block;
}

Nos quedaría:



Para que el mensaje aparezca siempre en la esquina inferior izquierda del elemento, hemos definido la posición como:

position: absolute;
top: 100%;
left: 100%;

El problema de este método es que el HTML queda confuso y poco semántico porque el span del mensaje se incluye dentro del elemento. Si queremos ponerlo fuera tenemos que "envolverlos" en un div o span extra de forma que sea este elemento el que muestra el tooltip al hacer hover con el ratón y nos sirva como marco para que aparezca posicionado con respecto al elemento.

//HTML
<div class="tooltipWrapper">
<button>Press</button>
<span class="tooltip">This is a tooltip</span>
</div>
//CSS

.tooltipWrapper {
  display: inline-block;
  position: relative;
}

.tooltip {
  position: absolute;
  display: none;
  border: 1px solid black;
  padding: 5px;
  background-color: gray;
  color: white;
  width: 100px;
  left: 50%;
  top: 100%;
}

.tooltipWrapper:hover .tooltip {
    display:block;
}

El display: inline-block se pone para que el div se ajuste al tamaño del contenido. Si no lo ponemos tendría todo el ancho del navegador. En la siguiente imagen se puede ver cómo quedaría. El div.tooltipWrapper está marcado con un borde rojo para verlo:



En esta ocasión hemos puesto left: 50% para que aparezca más centrado.
Si queremos mostrar el mensaje sobre una imagen necesitamos este div envolvente ( wrapper ) porque la etiqueta <img> no encierra contenido:

<div class="tooltipWrapper">
<img src="myImage.jpg" />
    <span class=".tooltip">This is a tooltip</span>
</div>
Con el mismo CSS del ejemplo anterior obtenemos:



Jugando con los estilos podemos darle el aspecto que queramos, incluso incluir transiciones o animaciones para para que se muestre más lentamente o vaya variando la opacidad ( en los navegadores que lo soporten ). En el siguiente ejemplo utilizamos el pseudo elemento :before para crear el pequeño triángulo que apunta al elemento y hacerlo similar al de Google que mostramos en la primera imagen:
//HTML
This is a tooltip
//CSS .tooltipWrapper { display: inline-block; position: relative; } .tooltip { position: absolute; display: none; border: 1px solid black; padding: 5px; background-color: gray; color: white; width: 100px; left: -50%; bottom: -37px; } .tooltip:before{ content: ' '; display: block; position: absolute; left: 45px; top: -8px; width: 14px; height: 14px; border-color: black; border-width: 1px; border-style: solid none none solid; background-color: gray; transform: rotate(45deg); -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); -moz-transform: rotate(45deg); } .tooltipWrapper:hover .tooltip { display:block; }
El resultado es:


Limitaciones del método tradicional

Como hemos visto no es muy semántico porque tenemos un elemento extraño dentro del contenido del botón (el span) o un elemento extra que envuelva el elemento y el tooltip. Podemos solucionarlo utilizando CSS3 para coger el valor de un atributo HTML y colocarlo como contenido de un elemento.

Tooltips con CSS3 utilizando un atributo de HTML para el contenido

Este método es mucho más semántico porque no necesitamos el span para el contenido del tooltip ni el div extra que lo envuelve. Utilizamos un atributo personalizado de HTML del tipo data- para el mensaje y el pseudo-elemento ::before para construir el tooltip:
//HTML
<button class="tooltip2" data-tooltip="this is the tooltip!">
    Press
</button>    

//CSS
.tooltip2 {
  position: relative;
}

.tooltip2::before {
    position:absolute;
    display: none;
    content: attr(data-tooltip) ;
    border: 1px solid black;
    padding: 3px;
    background-color:gray;
    color: white;
    white-space:nowrap;
    left: 50%;
    top: 100%;
 }

.tooltip2:hover::before {
    display: block;

 }




El texto del tooltip se coge del atributo data-tooltip mediante la línea CSS3:
content: attr(data-tooltip) ;
Podriamos utilizar title como vimos al principio, pero aparecería nuestro mensaje y el del navegador al mismo tiempo.
Recordemos que tenemos aún un pseudo-elemento extra disponible ( ::after ) por si queremos añadir también el pequeño triángulo que apunta al elemento.

El inconveniente de tener el texto en el atributo es que deben ser textos sencillos. No podemos crear textos complicados  con formato o imágenes.

Fuentes:
W3 Org title definiton
Simple cross-browser tooltip with CSS
Tooltips courtesy of HTML5 data attributes

domingo, 3 de febrero de 2013

Evaluación perezosa de operadores && y || (lazy evaluation)

¿Que es la evaluación perezosa?

La evaluación perezosa consiste, como siempre que hablamos de pereza, en hacer el mínimo trabajo posible. Es decir, una expresión no se evalúa hasta que realmente se necesita.

JavaScript lo utiliza sólo puntualmente, pero hay lenguajes, como Haskell, que lo utilizan siempre. Podemos ver la diferencia con una función que tiene otras funciones como parámetros (función de orden superior):

function ( func1(), func2(), func3() ) {
   ...
}

Un lenguaje sin evaluación perezosa ejecutaría las funciones func1(), func2() y func3() para obtener los parámetros finales y despues continuaría con el cuerpo de la función. En cambio un lenguaje con evaluación perezosa empieza con el código de la función sin evaluar previamente las tres funciones. Las irá evaluando cuando aparezcan en el código y sean realmente necesarias. De esta forma, si un argumento no se utiliza, nunca será evaluado ( puede ocurrir que alguno de los parámetros esté dentro de un if que no se cumple ).

JavaScript no es realmente un lenguaje que presente lazy evaluation , salvo para los operadores && y || como veremos a continuación. Haskell sí es un lenguaje puramente 'perezoso' y en el ejemplo anterior no llamaría a las funciones de los parámetros hasta que fuera necesario.

Lo contrario de lazy evaluation es eager evaluation ( algo así como evaluación ansiosa ) y es lo más habitual en los lenguajes de programación.

Comportamiento de && y || en JavaScript

En JavaScript los operadores || y && funcionan como lazy evaluation, aunque en realidad a este comportamiento con los operadores lógicos se le llama evaluación mínima o evaluación de cortocircuito ( short circuit evaluation ). Veamos un ejemplo:

var test = ("a" === "b") || (1===1) || ("c" === "h");  

//test is true

Las expresiones lógicas se evalúan siempre de izquierda a derecha. Sabemos que con el operador ||, cuando uno de los términos sea true el resultado será true. Esto significa que cuando encontramos uno verdadero ya no es necesario evaluar nada más, podemos asignar el valor a la variable 'test' sin necesidad de hacer más trabajo. Este es exactamente el comportamiento de JavaScript.

En el ejemplo anterior, la primera expresión ("a" === "b") es falsa, por lo que JavaScript evaluará la siguiente. (1===1) es verdadera, con lo que el resultado final ya está claro, no se evaluará ninguna expresión más.

En el caso del operador &&, cuando uno de los términos sea false el resultado será false:

var test = ("a" === "b") && (1===1) && ("c" === "h");

//test is false

Como ("a" === "b") es falsa, no se evalúa nada más, directamente se asigna test = false.

Este comportamiento, unido a la peculiaridad de que estos operadores pueden devolver valores no booleanos, hace que puedan utilizarse como una forma simplificada de ejecución condicional, como veremos a continuación.

&& y || pueden devolver valores no booleanos

Hay una particularidad más de JavaScript con respecto a estos operadores ( && y || ): que devuelven el valor real del término que se evaluó como true or false, aunque no sea booleano. En los ejemplos que vimos antes, todos los términos devolvían al final a un valor booleano, pero puede no ser así. Podemos tener funciones, objetos, strings o valores de otro tipo:

var number = 0;
var test =     ( 12 * number ) || "hola" || getPrice() ; 

//test is "hola"

La clave es que cualquier valor en JavaScript es convertible implícitamente en booleano cuando está en una expresión lógica. Sólo los siguientes valores son false:

  • 0
  • ""
  • false
  • NaN
  • null
  • undefined

Todos los demás valores se convierten en true.

En la expresión de arriba tenemos estos 3 términos:

( 12 * 0 ) //el resultado es 0 que se convierte implícitamente en false

"hola" // true

getPrice() // depende del resultado, pero no se ejecutará nunca

Como JavaScript devuelve el valor real de la última expresión que evalúa y no su conversión implícita a booleano, devuelve "hola".

Este comportamiento se utiliza, por ejemplo, para dar valores por defecto a variables:

var nombre = param1 || "Desconocido";
//Si `param1` no existe se asigna el valor "Desconocido"

o para asegurarnos de que un objeto existe antes de acceder a alguna de sus propiedades:

var nombre = empleado && empleado.getName();
//si el objeto 'empleado' no existe la segunda parte no se ejecuta y así evitamos un error.

¿Porqué no se utiliza siempre la evaluación perezosa?

A pesar del término perezosa, realmente este tipo de funcionamiento puede ser más costoso, porque requiere llevar un control del estado de las expresiones, para saber si están ya evaluadas o es la primera vez que se encuentran. Cuando no se utiliza no tenemos que llevar este tipo de control, porque las expresiones se evaluan siempre.

Además implica una cierta perdida de control porque no es evidente qué parte del código se va a ejecutar en cada caso. Esto puede suponer un problema cuando necesitamos controlar estados y puede llevar a errores difíciles de encontrar.


Fuentes:

Wikipedia: Evaluación de cortocircuito
Lazy evaluation - evaluación perezosa
Lazy evaluation
Why isn't lazy evaluation used everywhere



sábado, 19 de enero de 2013

Debounced Functions

John Hann, en una entrada en su blog propone un concepto muy interesante que llama debouncing. El termino no tiene una traducción exacta en español, sería algo así como eliminar rebotes. Es un término sacado de los circuitos electromecánicos. Cuando un contacto se cierra se producen micro-rebotes (bounce significa 'rebotar') que hacen que el circuito esté cerrado/abierto varias veces antes de que se cierre definitivamente. Estas múltiples señales deben eliminarse para enviar una sola, de circuito cerrado cuando el estado es estable.

Se envía una sola señal cuando el estado se ha estabilizado


En el mundo JavaScript el concepto se aplica para funciones que queremos que se ejecuten una sóla vez aunque el evento que las dispara se lance cientos de veces en un determinado periodo.

Un ejemplo sencillo de uso, sacado del blog de Paul Irish, sería un callback para el evento resize. Firefox lanza sólo un evento cuando el usuario ha terminado de redimensionar la ventana, pero los demás navegadores lanzan eventos continuamente mientras la ventana se está redimensionado. Para ejecutar la función sólo una vez en todos los casos, podemos llamar a un debounced callback  que sólo se ejecutará una vez.

Básicamente se trata de que durante un determinado lapso de tiempo (configurable) se absorban todas las llamadas a la función y se ejecute sólo una. La función sólo se lanza cuando las llamadas se han estabilizado durante este lapso de tiempo, es decir una vez que ha pasado el tiempo fijado y no se ha producido una nueva llamada.

Es muy útil para eventos relacionados con el arrastre del ratón, que se disparan continuamente mientras el ratón se está moviendo.

Podemos crear una utilidad que nos devuelva una versión 'debounced' de una función cualquiera. Esta sería la función, sacada del ejemplo de Paul Irish que comentaba antes:
  var debounce = function (func, threshold, execAsap) {
      var timeout;
      return function debounced () {
          var obj = this, args = arguments;
          function delayed () {
              if (!execAsap)
                  func.apply(obj, args);
              timeout = null; 
          };
 
          if (timeout)
              clearTimeout(timeout);
          else if (execAsap)
              func.apply(obj, args);
 
          timeout = setTimeout(delayed, threshold || 100); 
      };
  }
Los parámetros son los siguientes:
  • func: La función original que queremos convertir en 'debounced' 
  • threshold: El lapso de tiempo, en ms, durante el que tienen que estabilizarse las llamadas ( no producirse ninguna nueva). Es opcional, 100ms es el valor por defecto. 
  • execAsap: Opcional. Por defecto la función se ejecuta al final del periodo de estabilización, con este parámetro a *true* la función se ejecuta inmediatamente, antes del periodo de estabilización

Si, por ejemplo, tenemos una función showMessage y queremos usarla como debounced callback para el evento resize, podemos hacer:

$(window).resize( debounce( showMessage) );


lunes, 7 de enero de 2013

Lo peor de JavaScript

El libro de Douglas Crockford "JavaScript, the good parts" incluye un apéndice con las que son, a su juicio, las peores partes del lenguaje. Este es un resumen de esos puntos negros que conviene conocer y evitar (algunos se han solucionado en ECMAScript 5).

Variables globales

Las variables globales son variables visibles en cualquier contexto y el problema más grave de JavaScript es su dependencia de ellas. Al no existir un compilador, la única manera de enlazar unos scripts con otros es mediate el uso del contexto global.

Este tipo de variables pueden ser modificadas por cualquier script en cualquier momento, incluyendo una librería externa que puede utilizar el mismo nombre de variable que nosotros para otro propósito.

Para agravar el problema, cualquier variable que se declare sin var, en cualquier contexto, se convierte automáticamente en global.

myVar = "hello";   //myVar es una variable global

Contexto

El contexto (scope) de una variable en JavaScript es la función en la que está declarada (o el global si no está dentro de ninguna).



function test() {
  var one = 1;
  
  // var one is visible inside the function

}

// var one is not visible here

En la gran mayoría de los lenguages de programación, las variables tienen visibilidad sólo dentro del bloque en el que se crean ( un bloque se delimita entre llaves ). La sintaxis de JavaScript permite definir bloques, pero no proporciona un contexto de bloque para sus variables.


if ( valid ) {
    var myVar = "this is valid";
    …
    //esto es un bloque
    //myVar está declarada en este bloque
    //pero será visible fuera de él
    //No tiene un contexto de bloque

}  
  // myVar es también visible aqui

Inserción automática de punto y coma

El punto y coma al final de una sentencia no es necesario en JavaScript, el intérprete lo inserta automáticamente. Esto parece cómodo pero en realidad puede dar lugar a errores muy difíciles de encontrar al colocar el ';' en lugares incorrectos.

Por ejemplo, si queremos devolver un objeto en una función y lo escribimos así:


…
…
return
{
    result: true
};


En realidad estaremos devolviendo undefined porque el punto y coma se inserta justo detras de return. No habrá warning ni error. Para evitar estos problemas es recomendable incluir siempre los ';' cuando escribimos el código.

Palabras reservadas

JavaScript tiene más de 60 palabras reservadas de las que la mayoría no se utilizan realmente en el lenguaje. El problema es que no pueden utilizarse como nombres de variables, parámetros, objetos o funciones.

Si queremos utilizarlas como propiedades de un objeto, deben ir entre comillas y no puede utilizarse la notación '.' para referenciarlas:


var myObject = { 'final': true}; //final es una palabra reservada
myObject.final = false;          // Error
myobject['final'] = false;       // Correcto

Unicode

La implementación de Unicode en JavaScript es obsoleta. Se diseñó para representar un total de 65.563 caracteres pero actualmente Unicode soporta más de 1 millón.

El problema es que JavaScript representa siempre los caracteres con 16 bits. Cuando se necesitan más bits para representarlo ( por encima de 65.563 ) JavaScript lo considera como 2 caracteres distintos, cada uno con 16 bits.

typeof

El operador typeof tiene graves fallos que nos obligan a manejarlo con mucho cuidado. Por ejemplo, si hacemos:

typeof null;   //returns "object"

Nos devolverá "object". Un resultado que no es lógico ni es de mucha ayuda. El operador también devuelve "object" para un array.

Para expresiones regulares como:

typeof /a/;  

El resultado varía. En algunas implementaciones se devuelve "object" y en otras "function".

parseInt

la función parseInt(string, radix) se utiliza para transformar un string en un entero. El parámetro opcional radix indica la base en la que está representado el entero.

Cuando se utiliza la función sin indicar radix es facil que se produzcan errores porque cualquier string con un "0" delante se evalua como un entero en base 8.

parseInt("09"); // 0 porque el 9 no existe en base 8
parseInt("046"); // 38, es 46 en base 8 
parseInt("046", 10); // 46. Indicamos explicitamente que trabajamos en decimal

Para evitar estos errores es recomendable indicar siempre el radix. ECMAScript 5 corrige este problema considerando siempre que la base es decimal por defecto.

Operador +

El operador + está sobrecargado en JavaScript y puede sumar números o concatenar strings, dependiendo únicamente del tipo de los parámetros.

  • Si cualquiera de los operandos es un string vacio, devuelve el otro operando convertido en un string.
  • Si los dos operandos son números devuelve la suma.
  • En cualquier otro caso convierte ambos operandos en strings y los concatena.

Este comportamiento es una fuente de errores.

Números en coma flotante

JavaScript implementa el estándar IEEE 754 ( Binary Floating-point Arithmetic ) que no es adecuado para el manejo de fracciones. Produce pequeños errores debidos al redondeo.

Una forma de evitar los errortes es intentar trabajar siempre con enteros con un factor de escalado.

NaN

NaN significa "Not a Number" y es un valor definido en el estandar IEEE 754 del que hablamos antes.

Lo curioso de NaN son los siguientes comportamientos:

    typeof NaN === 'number';     //true
    NaN === NaN;                 //false
    NaN !== NaN;                 //true
    

Es decir, typeof NaN nos indica que es un número pese a que representa exactamente lo contrario. Ademas, NaN no es igual a si mismo.

Arrays

JavaScript no tiene arrays reales, con memoria consecutiva reservada para los elementos. Los implementa como objetos cuyos índices son enteros. Esto tiene dos consecuencias negativas:

  • El rendimiento es bastante peor que con arrays reales
  • El operador typeof no distingue entre objetos y arrays

Valores false

Los siguientes valores son false en JavaScript:

  • 0
  • NaN
  • ""
  • false
  • null
  • undefined

La confusa diferencia entre null y undefined puede llevar a error. Lo más grave es que undefined y NaN no son constantes, son variables globales y su valor puede modificarse causando errores en el resto de los scripts de la página.

hasOwnProperty

Este método puede utilizarse para filtrar, en un bucle for in, las propiedades de un objeto que vienen heredadas de su prototipo. El problema es precisamente que es un método y no un operador. Cualquier objeto puede reemplazarlo por su propia versión o por cualquier valor, haciendolo poco fiable.

Object

El problema en este caso es que los objetos en JavaScript nunca están realmente vacios. Heredan las propiedades de la cadena de prototipos. Si no tenemos esto siempre en cuenta podemos incurrir en errores provocados por propiedades/métodos con los que no contamos.

miércoles, 26 de diciembre de 2012

La detección del navegador y sus problemas(browser detection)

Detectar el navegador vs detectar funcionalidades

Detectar el navegador que está utilizando el usuario mediante una búsqueda del string correspondiente ("MSIE", "Firefox", etc) en la cadena de texto guardada en navigator.userAgent ha sido una práctica común durante años. Se ha utilizado, por ejemplo, para ejecutar un código diferente según el navegador.

if ( navigator.userAgent.indexOf("MSIE") !== -1 )
  {
    // Run custom code for Internet Explorer.
  }
else {
    // Run code for other browsers
} 
Un ejemplo clásico es el objeto utilizado para AJAX. Durante muchos años el IE ha utilizado ActiveXObject mientras el resto de navegadores utilizaban XMLHttpRequest. Era necesario obtenerlo de forma diferente.

Hoy en día esta detección tan simple está descartada porque estamos asumiendo que cualquier versión futura de MSIE tendrá el mismo problema. En el momento que una versión de IE cambie y utilice el objeto estándar nuestro script fallará.

Podemos hacer un script más complejo y detectar también la versión del navegador, pero, como veremos más adelante, el uso de navigator.userAgent no es recomendable. Para saber si podemos usar una determinada funcionalidad o no, debemos probar si esa funcionalidad concreta está presente. Esto es lo que se llama detección de funcionalidades o detección de capacidades.

La forma correcta de saber si podemos utilizar XMLHttpRequest sería:

if ( window.XMLHttpRequest ) {

   // Use XMLHttpRequest
}
else {

    // Use ActiveXObject
} 
De esta forma nuestra prueba es independiente del fabricante/versión del navegador.

Los problemas de utilizar el user-agent


Esta cadena tiene valores del tipo:

Chrome: "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"

IE: "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"

Safari: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10"

Estos son sólo unos ejemplos puntuales, pero el texto será diferente según versión, sistema operativo, etc. Hay cientos de user agents diferentes, no solo por las versiones de navegadores, también están los robots de los buscadores ( crawlers ), los lectores de feeds, los clientes de correo, los validadores de código, librerías, etc. En la web http://www.useragentstring.com puede verse una lista completa.

Normalmente la detección, como vimos antes, consiste en localizar el nombre del navegador en este string. Un ejemplo simplificado en JavaScript:

if ( navigator.userAgent.indexOf("Chrome") !== -1 ) {
    alert ("El navegador es Chrome")
}

Igualmente podríamos buscar el string "Firefox" o "MSIE" para detectar otros navegadores. El problema es que el formato del texto no sigue un estándar unificado. Es necesario hacer una búsqueda del nombre del navegador en toda la cadena. Es necesario también saber con que nombre aparece cada navegador.

Además del problema de bloquear versiones futuras (y corregidas) del mismo navegador, la información del user-agent no es fiable. Hay muchos navegadores que utilizan el nombre de otros para que no se les bloquee o se les sirva una versión diferente del sitio web. Por ejemplo Opera puede contener el string "MSIE". Internet Explorer utilizó durante años el string "Mozilla" para indentificarse como Netscape, Chrome incluye el string "Safari" para 'disfrazarse' en los dispositivos de Apple.

Las razones y la historia detrás de los múltiples cambios, parches y añadidos del user-agent puede leerse en el divertido artículo History of the browser user-agent string.

Debido a este empeño de los navegadores en ofuscar su identidad, los scripts de detección se han hecho más complejos porque es necesario saber qué texto buscar y qué texto excluir para cada caso. Por ejemplo, para saber si un navegador es Safari no basta con buscar ese texto, tenemos también que comprobar que no aparezca "Chrome". Además es recomendable localizar también la versión del navegador, que puede aparecer en lugares distintos según el fabricante. Por todo esto es recomendable utilizar un script probado como el de [Quirksmode].

Además de todo esto un usuario puede manipular y cambiar muy facilmente el valor del user-agent en su navegador.

Esta técnica ha sido relegada a casos muy excepcionales y no debe usarse nunca cuando lo que queremos saber es si una funcionalidad o capacidad concreta está disponible. Para estos casos, la detección de funcionalidades es mucho más apropiada.

Detección de funcionalidades (feature detection)

Las diferencias entre navegadores aún nos obligan a realizar algún tipo de detección antes de utilizar algunas funcionalidades implementadas de forma diferente. Por ejemplo, si queremos capturar un evento del DOM ( para asignarle una función, por ejemplo ) tendremos que utilizar el método attachEvent() para IE<9 o el método addEventListener() para otros navegadores. Lo correcto es detectar si el método en cuestión está disponible:

var element = document.getElementById( “target” );
if ( element.addEventListener ) {
        element.addEventListener( "click", function, false);
} else if ( element.attachEvent ){
        element.attachEvent("onclick", function);
}

De forma parecida podemos saber si el navegador soporta algunas de las características más novedosas de la familia HTML5.

LocalStorage

if (!!window.localStorage) {
   /* you have localstorage */
}
nota: !! sirve sólo para forzar una conversión de tipo a booleano.

canvas

if ( !!document.createElement("canvas").getContext ) {
   /* you have canvas*/
}

atributo placeholder en los input de texto

var test = document.createElement('input');
if ( 'placeholder' in test ) {
    // you have placeholder attribute for inputs
}
Como vemos en estos ejemplos anteriores, la clave está en encontrar la forma de detectar la capacidad concreta que necesitamos. Debemos relegar la búsqueda en el user-agent a problemas muy puntuales, por ejemplo cuando una característica está soportada pero el soporte es incorrecto o muy ineficiente.

Fuentes:
Feature detection: state of the art browser scripting
Stack Overflow: What is the best way to do browser detection
Stack Overflow: Best way to detect browser
Browser detection is bad

domingo, 2 de diciembre de 2012

Memoization en JavaScript

Se trata de una técnica que mejora el rendimiento de una función utilizando una caché para guardar los resultados de invocaciones previas. De esta forma, cuando se vuelve a llamar a la función con el mismo parámetro, se devuelve el resultado de la caché, sin necesidad de recalcular de nuevo.

En realidad estamos mejorando el tiempo de respuesta de la funcion a cambio de ocupar más espacio de memoria. Los resultados se van guardando en un tabla hash (un objeto) indexados por el parámetro o parámetros con los que se ha producido ese resultado.

Podemos limitar el tamaño del objeto de resultados a un máximo e incluir algun algoritmo de sustitución de entradas. En algunos casos puede ser conveniente tener una tabla de resultados fija, inicializada de antemano, con los argumentos que sabemos que se van a dar en una amplia mayoría de las llamadas.

Ejemplo. Factorial de un número

Como sabemos el factorial se define por:

0! = 1
n! = n * (n-1)!

Podemos hacerlo con una función recursiva muy sencilla:


function factorial (n) {
  if (n <= 0) return 1;
  return n * factorial(n-1);
}

Este es un ejemplo de función que se beneficiaría enormemente si utilizaramos memoization para cachear resultados de operaciones anteriores.

Si hacemos factorial(3)la función tendrá que hacer el calculo, pero si luego hacemos factorial(5) de nuevo tendra que calcular ( recursivamente ) el factorial de 4, de 3, de 2 y de 1. Como el factorial de 3 ya lo hemos calculado antes, podriamos aprovecharlo ahorrando procesamiento.

Para aprovechar los calculos anteriores introducimos una caché local que almacenará los resultados.


var cache = {};

function factorial (n) {
    
    if (n <= 0) return 1;
    
    if ( cache[n] ) {
        return cache[n];
    }
    else {  
        cache[n] =  n * factorial(n-1);
        return n * factorial(n-1);
    }   
}

Hemos utilizado una cache externa porque debe permanecer cuando la función haya terminado. Podemos aprovechar que en JavaScript las funciones son también objetos para definir la caché como una propiedad de la función:


function factorial (n) {

    //creamos nuestra cache solamente si no está ya definida
    if ( !factorial.cache ) factorial.cache = {};
    
    if (n <= 0) return 1;
  
    if (factorial.cache[n]) {
        return factorial.cache[n];
    }
    else {
        factorial.cache[n] =  n * factorial(n-1);
        return factorial.cache[n];
  }     
}

Cuando la función tiene varios parámetros tenemos que combinarlos de alguna forma para formar un indice único y válido en la tabla de resultados.

En proyectos grandes, con muchos métodos apropiadas para aplicarles esta técnica, es util crear una función memoizadora a la que pasamos nuestra función y nos devuelve una nueva, igual que la original, pero con memoization.

Fuentes:
Implementing memoization in JavaScript
JavaScript Memoization
Speed-up your JavaScript
Memoization. Una primera mirada

domingo, 4 de noviembre de 2012

Hacer un mosaico/galería con HTML y CSS

Un mosaico o galería de imágenes es un elemento básico que utilizado en multitud de aplicaciones web:

galería de fotos de Picasa
Esta distribución de elementos es muy sencilla de realizar con HTML y CSS. Vamos a ver a continuación algunos ejemplos y algunas de las particularidades que nos podemos encontrar.

El ejemplo que vamos a hacer va a ser el siguiente:

Siempre vamos a tener un contenedor, que en la imagen anterior vemos con un borde punteado, dentro del cual que colocan los elementos.

Los elementos pueden ser cualquier cosa, desde una simple imagen, como en la galería de Picasa, hasta un div conteniendo textos, imágenes y otros div's interiores, como en la galería de películas de Youzee:


En nuestro ejemplo el elemento es un div con otro div interno, con los bordes redondeados hasta hacer un círculo, y un número. Como el contenido del elemento no es importante para este tutorial, simplemente lo representaremos como:

1


El HTML para nuestros 8 elementos sería:
1
2
3
4
5
6
7
8
No tiene ninguna dificultad, es un div contenedor envolviendo a los elementos que formarán la galería. También es muy común ver esta misma estructura con una lista <ul> como contenedor y los items <li> de la lista como elementos. Lo veremos más adelante.

Con lo que realmente construimos la galería es con el CSS:
#container {
  border: 1px dotted black;
  overflow: hidden;
  width: 450px;
}

.element {
  width: 100px;
  height: 150px;
  float: left;
  margin : 5px;
}


Lo más importante en estas pocas líneas de CSS es que los elementos tienen float: left. Esto es lo que hace que se coloquen como un mosaico. El hecho de que sean float nos obliga a poner overflow:hidden en el contenedor para que los envuelva y no se quede vacío (aunque hay otras soluciones ).

El margin de 5px de los elementos sirve para que no queden pegados y tengan esa pequeña separación entre ellos, tanto por arriba como a los lados.

También vemos que hemos dado una anchura fija al contenedor para que quepan justo cuatro elementos, pero no tiene que ser necesariamente así. Es una galería autoajustable y podemos dejarlo sin anchura fija para que ocupe todo el ancho disponible, los elementos se redistribuirán automáticamente:


Podemos fijar una anchura mínima y/o máxima para que se quede siempre dentro de unos márgenes aceptables para el diseño gráfico de nuestra aplicación:

#container {
  border: 1px dotted black;
  overflow: hidden;
  min-width: 230px;
  max-width: 700px;
}

Con los valores anteriores conseguimos que el diseño sea líquido pero que nunca se expanda más de seis columnas ( con sus respectivos márgenes ) o se contraiga hasta contener menos de dos columnas.

También podríamos utilizar display: inline-block para los elementos, pero presenta un pequeño inconveniente cuando tenemos que hacer diseños con unas especificaciones muy concretas (márgenes, dimensiones, etc). El problema es que dejan un pequeño margen extra entre elementos. Esto lo veremos más detalladamente en otra entrada.

Utilizando una lista

Como hemos comentado antes, se puede utilizar una lista para hacer la galería. Algunos programadores argumentan que el HTML resultante es más semántico, aunque tengo mis dudas sobre este punto.

El código es muy parecido al anterior. El HTML sería:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
Y el CSS:
#container {
  border: 1px dotted black;
  overflow: hidden;
  width: 450px;
  padding: 0px;

}

.element {
  list-style: none;
  width: 100px;
  height: 150px;
  float: left;
  margin : 5px;
}

El CSS tiene dos novedades importantes. Primero tenemos que resetear el padding de nuestra lista <ul>, con el id #container, porque algunos navegadores fijan un padding izquierdo por defecto que haría que el mosaico quedara un poco desplazado a la derecha.

El segundo detalle a tener en cuenta es que tenemos que eliminar el 'disco' que normalmente tienen a la izquierda los elementos de una lista. Para esto hacemos list-style: none.

El resultado es el mismo que en el ejemplo anterior.