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.

jueves, 1 de noviembre de 2012

¿Por qué se necesitan paréntesis para hacer eval() de JSON?

Como vimos en otra entrada, cuando recibimos un JSON tenemos que hacer que el motor de JavaScript lo interprete para convertir el string en objetos JavaScript nativos. Una forma de hacer esto ( poco recomendable, ver el tutorial ) es utilizar la función eval():

eval (‘(‘ + myJsonString + ‘)’ );

Como vemos en el ejemplo, para asegurarnos de que el código se evalúa correctamente es necesario añadir unos paréntesis que lo envuelvan. La razón es que cuando evaluamos cualquier string se lo estamos pasando al parser de JavaScript para que lo interprete. Cuando el parser encuentra una llave ‘{’ al principio de una instrucción lo interpreta como el inicio de un bloque, no como el inicio de un objeto.

Normalmente, en un código JavaScript, un objeto no aparece aislado, sino como parte de una expresión:

var test = { name: “Pepe”};

La llave no está al principio. Cuando se encuentra al principio el parser espera un bloque de instrucciones:

{
    var one = 1;
    var two = “dos”;
    myFunction();
}

Al esperar un conjunto de instrucciones y encontrarse con algo del tipo:


“clave1”:  valor1,
“clave2”:  valor2

lanzará un error. Es símplemente la forma en la que funciona el parser al interpretar un código JavaScript.

Para evitar que se tome como bloque y se evalúe correctamente, debemos hacer que el objeto forme parte de una expresión. Lo podemos conseguir simplemente encerrándolo entre paréntesis:


( { “name” : “Pepe”} )

Después de encontrar un paréntesis el parser evalúa la llave ‘{‘ como el comienzo de un objeto literal, que es lo que necesitamos.

viernes, 26 de octubre de 2012

Tutorial de JSON

JSON (JavaScript Object Notation) es un formato estándar para intercambio de datos que prácticamente ha sustituido a XML como el formato preferido para traspaso de datos entre cliente y servidor web.


Es una sintaxis estandarizada, un conjunto de reglas en el que nos hemos puesto de acuerdo para poder pasar información sabiendo como codificarla y decodificarla.

Se basa en la notación literal de JavaScript para estructurar datos en arrays y en objetos. Es una forma sencilla de 'empaquetar' información para enviarla:

{
"title": "The wonderful guide",
"pages": 348,
"price": 15
}

A pesar de llevar JavaScript integrado en el acrónimo, es un estándar independiente definido en la RFC 4627 y puede utilizarse en cualquier lenguaje.

El sustituto de XML

AJAX revolucionó la forma en la que se desarrollan aplicaciones web. En vez de pedir al servidor páginas completas cada vez que algo cambia, se pide sólo la información que ha cambiado. Es decir, se piden datos.

Se hizo necesario un formato para intercambio de datos, para enviarlos desde el servidor, que tiene la base de datos, al cliente que se encarga de presentarlos. El formato elegido fue, en principio, XML. Parecía tan apropiado que incluso forma parte de las siglas AJAX (Asynchronous Javascript And XML).

Douglas Crockford propuso, en el año 2001, JSON como una alternativa más ligera, sencilla y eficiente porque encontraba que XML no era muy apropiado para esta tarea. El nuevo formato se popularizó rápidamente y se ha convertido en el estándar de facto para este tipo de transferencias.

JSON es más sencillo de codificar y más facil de leer, pero probablemente el factor más importante para su éxito es que utiliza unas estructuras de datos que son nativas para JavaScript, el lenguaje del cliente (no olvidemos que es la 'J' de AJAX).

¿Cómo codifico datos en JSON?

Para enviar datos en JSON debemos organizarlos de alguna de estas dos formas:

  • Un conjunto de pares clave/valor entre llaves { clave1: valor1, clave2: valor2 } (un objeto en JavaScript)
  • Una lista de valores entre corchetes [valor1, valor2] (un array en JavaScript)

Sólo existen estas dos opciones, cualquier dato tiene que enviarse de esta forma (En realidad lo que se envía es un string con esta estructura).

Por ejemplo, supongamos que sacamos de la base de datos la información de un empleado: nombre, edad y cargo. Para enviarlo como JSON válido tendriamos que colocarlo con la siguiente estructura:

{
 "nombre": "Pepe",
 "edad": 22,
 "cargo": "botones"

}

También podríamos ponerlo en forma de array, pero en este caso tiene menos sentido:


["Pepe", 22, "botones"]

Lo que sí tiene sentido es utilizar un array para enviar los datos de varios empleados:

[
    {
      "nombre": "Pepe",
      "edad": 22,
      "cargo": "botones"
    },
    {
      "nombre": "Julio",
      "edad": 32,
      "cargo": "albañil"
    },
    {
      "nombre": "Tim O'Theo",
      "edad": 28,
      "cargo": "detective"
    }
]   

Objetos y arrays se pueden anidar hasta crear estructuras muy complejas.

Aunque tengamos un único dato debe enviarse tambien de esta forma, como un objeto de una sóla propiedad o un array de un sólo elemento:


["Pepe"]

o

{ "nombre": "Pepe"}

Como podemos ver en estos ejemplos, las claves de los pares clave/valor tienen que ir siempre entre comillas dobles.

Los valores pueden ser: objetos, arrays, números, strings, booleanos o null.

Usando JSON en JavaScript

No olvidemos que lo que se envía/recibe es siempre un string. Por eso cuando enviamos del cliente al servidor, aunque JavaScript ya tiene los objetos en el formato correcto (nativo), hay que serializarlos para enviarlos.

Lo que se envía es:

'{ "nombre": "Pepe", "edad": 22, "cargo": "botones" }'

Al recibir JSON en JavaScript (de una petición AJAX, por ejemplo), debemos convertir el string en un objeto/array real. Como la sintaxis es idéntica a la de un objeto/array en JavaScript, lo único que tenemos que hacer es 'decirle' al motor de JS que lo interprete. Esto se hace mediante eval("my string") ( por motivos de seguridad es mejor utilizar el objeto JSON como veremos más adelante).

Por ejemplo, si tenemos recibido el JSON del ejemplo anterior mediante AJAX, en la variable ajaxData, basta con hacer:

var empleado = eval ('(' + ajaxData + ')') 

Los dos paréntesis que añadimos son necesarios para que JavaScript interprete correctamente las llaves con las que puede comenzar un string JSON. Lo veremos más detalladamente en otro post.

Ahora ya podemos acceder a todos los datos internos de la forma habitual:

alert ( empleado.name );
alert ( empleado.edad);

Debido a los problemas de seguridad que puede provocar el uso de eval(), se ha incluido en los navegadores, de forma nativa, el objeto JSON. Para 'evaluar' un string JSON que hemos recibido sólo tenemos que hacer:

var empleado = JSON.parse(ajaxData);

Y ya tenemos el objeto nativo en la variable empleado.

Si queremos convertir un objeto/array en un string para enviarlo como JSON hacemos:


JSON.stringify( empleado );

Si tenemos que dar soporte algún navegador antiguo que no tenga el objeto JSON ( IE6 e IE7 ) podemos utilizar alguna de las múltiples librerias que hacen lo mismo, como JSON2 o jQuery ( que proporciona el método .parseJSON() ).

Fuentes:
JSON
JSON, wikipedia
JSON: The Fat-Free Alternative to XML
Tutorial JSON

domingo, 30 de septiembre de 2012

Preservando el contexto *this*. $.proxy() y bind()

Como hemos visto en algún post anterior, el valor de this en una función representa el contexto en el que se ejecuta y depende de cómo se la llame. Cuando utilizamos la función como un callback, es muy probable que el valor de this no sea el que esperamos cuando finalmente se ejecuta.
Supongamos que tenemos el siguiente objeto:
var myObject = {
    myName: "Hulk",
    showMyName: function() {  alert(  "My Name Is: " + this.myName );}
}
Es muy importante que, cuando utilicemos showMyName() como callback, this siga haciendo referencia al objeto myObject, para que this.myName sea “Hulk”. Sin embargo, la mayoría de las veces no será así. Al ejecutarse desde otro contexto, el valor de this será diferente. Por ejemplo:

//como callback en setTimeout
setTimeout( myObject.showMyName, 1000); 

//como callback en el click de un botón
$(".button1").click( myObject.showMyName );


En los dos casos perdemos el contexto original y el resultado es:

“My Name Is: undefined”

En el primer caso this será el objeto global window y en el segundo caso será el elemento button del DOM. Ninguno de estos dos objetos tiene una propiedad llamada myName, por lo que this.myName es undefined en ese contexto.

A veces necesitamos asegurarnos de que un callback se va a ejecutar con el contexto que nosotros necesitamos. Es decir, que el valor de this no nos va a dar sorpresas y tendrá el valor que queramos. En cualquier proyecto grande necesitaremos una función que nos asegure esto.

Muchas librerias y frameworks proporcionan una solución propia a este problema, por ejemplo jQuery tiene la función $.proxy(), Prototype tiene una función bind(), Ext JS tiene createDelegate(), Dojo tiene hitch(), etc. La última versión de JavaScript, ECMAscript 5, ha incluido también el método .bind() como solución nativa.

Si tenemos que dar soporte a navegadores que no soportan bind() de ECMAscript 5 y no tenemos necesidad de incluir ninguna de estas librerías o frameworks, podemos crear nosotros mismos nuestra utilidad para esto.

Creando nuestra función proxy

Lo que tenemos que hacer es sencillo, envolver la función original en otra que nos asegure el contexto y que se pueda utilizar como callback en lugar del método original. Es decir, una función intermediaria ( proxy ).

Vamos a crear una función createProxyFunction( context, originalFunc ) con dos argumentos: el contexto que debemos preservar ( this ) y la función original. Devolverá una función que puede usarse exactamente igual que la original, pero asegurando el valor de this. El uso sería tan sencillo como:

var myProxyFunc = createProxyFunction( myObject,  myObject.showMyName ); 
$(".button1").click( myProxyFunc );


o, ahorrandonos la variable intermedia:

$(".button1").click( createProxyFunction( myObject,  myObject.showMyName) );


Podemos hacer esto simplemente utilizando los métodos call() o apply() que tienen todas las funciones en JavaScript. Devolveremos una función que llamará a la original utilizando apply() para asignar el contexto.

Una implementación simple sería:

function createProxyFunction ( context, originalFunc ) {
    var proxyFunction = function() {
        return originalFunc.apply( context, arguments );
};
return proxyFunction;
} 


Podríamos complicarlo un poco para permitir añadir argumentos ‘locales’ que se sumarían a los argumentos con los que se llamará al callback cuando se ejecute:

function createProxyFunction ( context, originalFunc ) {
    //store additional arguments (if any) appart from context and originalFunc 
    var proxyArgs = Array.prototype.slice.call(arguments, 2);
    var proxyFunction = function() {
        var allArgs = proxyArgs.concat ( Array.prototype.slice.call(arguments) );
        return originalFunc.apply( context, allArgs );
};
return proxyFunction;
} 


Utilizamos la función slice() de la ‘clase’ Array porque arguments es un pseudo-array y no la tiene. El valor que devuelve sí es un array real y por eso podemos utilizar concat().

La función que se devuelve en este caso tiene concatenados los argumentos que pasemos a createProxyFunction() (despues de context y originalFunc ) con los que se incluyan en la función original por parte de quien ejecute el callback. Es lo mismo que hace $.proxy() de jQuery.

¿Para qué sirven los argumentos extra?

La opción de añadir parámetros extra es muy útil porque podemos estar manejando información, externa al objeto que enviamos como contexto, que necesitaremos conocer cuando recibamos la llamada a nuestro callback.

Los parámetros que recibimos normalmente en un callback estarán fijados por un API. Por ejemplo, para un evento click, jQuery lanzará nuestro callback con eventObject como único argumento:

eventHandler( eventObject)

Si utilizamos la función $.get() de jQuery para pedir unos datos por AJAX, nuestro callback será invocado cuando los datos estén listos con los argumentos:

myAjaxCallback( data, textStatus, jqXHR )

Puede ocurrir que estemos manejando unos datos concretos y, en función de estos, hagamos una llamada AJAX. La respuesta de la llamada AJAX nos llegará cuando se invoque nuestro callback, pero necesitamos seguir teniendo acceso a los datos que estábamos manejando antes para terminar la tarea. La solución sería añadir estos datos como parámetros extra:

$.get('ajax/test.html', createProxyFunction( myObject, myAjaxCallback, extraParam1, extraParam2 );

Por supuesto la función myAjaxCallback tiene que estar preparada para recibir todos estos parámetros:

myAjaxCallback( extraParam1, extraParam2, data, textStatus, jqXHR ); 


ECMAScript 5 bind()

En la implementación de ECMAScript 5, bind() es un método nativo de las funciones que devuelve una nueva función con el contexto fijado. En nuestro ejemplo lo utilizaríamos así:

$(".button1").click( myObject.showMyName.bind( myObject ) );


También pueden añadirse parámetros extra si los necesitamos:

$(".button1").click( myObject.showMyName.bind( myObject, extraParam1, extraParam2 ) );


Recibiremos los parámetros extra delante:

callbackFunction ( extraParam1, extraParam2, param1, param2 );


En que se diferencia de myObject.showMyName.call( myObject )?

A veces se plantea la pregunta de si podríamos usar directamente:

$(".button1").click( myObject.showMyName.call( myObject ) );


en vez de :

$(".button1").click( myObject.showMyName.bind( myObject ) );


No es lo mismo. La diferencia es que bind() devuelve una función que es la que actúa como callback. Utilizando call() estamos ejecutando directamente el método, no sirve como callback. Las dos expresiones no son equivalentes.


viernes, 28 de septiembre de 2012

Ajustar el ancho de un DIV al contenido

Por defecto el ancho de un div (o de cualquier elemento de bloque) se expande hasta ocupar el 100% del espacio disponible. A veces podemos necesitar cambiar este comportamiento para que se ajuste automáticamente al ancho de su contenido:


Hay varias formas de conseguir esto pero todas son ‘pequeñas chapuzas’ y tienen efectos secundarios. CSS no proporciona una forma elegante de hacerlo.
En el estándar CSS, ajustar el ancho al contenido es sólo un recurso para las situaciones en las que se necesita asignar un ancho y no hay uno definido ni fijado por defecto. Lo que necesitamos es crear una de estas situaciones, que son:
  • float
  • posición absoluta
  • inline-block ( o inline)
  • table

Float

.myDiv {
    float:left;  /* o float:right;*/
}
Haciendo el elemento flotante conseguimos el objetivo, pero es necesario conocer muy bien las propiedades de los elementos flotantes porque los efectos secundarios afectan al comportamiento del div y al resto de contenido.

Posición absoluta

.myDiv {
    position:absolute;
}
Esta solución también tiene efectos secundarios importantes:

  • El div sale del flujo natural de elementos en la página y no ocupa espacio para el resto de elementos, que pueden descolocarse.
  • Se posiciona con respecto al elemento que lo contiene ( si éste está posicionado ).


Inline-block

.myDiv {
    display:inline-block;
}
Esta solución es, quizá, la más elegante. Hay un par de cosas que tenemos que tener en cuenta:
  • El elemento se colocará ‘en línea’ con otros elementos, si los hay.
  • Para que funcione en IE7 necesita un hack o cambiar el div por un span.
El problema con IE7 ( y con IE6 pero ya es hora de ir descartándolo ) es que sólo soportan inline-block para elementos que son inline por defecto. Por eso, a parte del hack, una solución sencilla es cambiar el div por un span y aplicarle display:inline-block. El resultado en el resto de navegadores será el correcto.

Table

.myDiv {
    display:table;
}
Dejando aparte el estigma que tiene la palabra table, este es un método que apenas tiene efectos secundarios. El único problema es que no funciona en IE6/7.

Ajustar al contenido es costoso (poco eficiente)

Siempre que podamos es preferible utilizar anchos fijos o anchos asignados por defecto. Para el navegador es un proceso mucho más sencillo. Para presentar un elemento de bloque que se ajuste automáticamente a su contenido debe primero calcular los tamaños de todos los contenidos y despues volver al elemento contenedor para calcular su anchura y presentarlo.


Fuentes:
CSS “shrink wrap”
StackOverflow Cross browser inline-block

domingo, 23 de septiembre de 2012

JavaScript: El contexto *this* en callbacks

El valor del puntero this en JavaScript es uno de los grandes enigmas con que los que se enfrenta el que empieza a manejar el lenguaje. En principio parece sencillo. Dentro de un objeto, el puntero this hace referencia al propio objeto ¿o no?:
var myObject = {
    message: “Hello!”,
    talk: function() {
        alert(“I say: ” + this.message);
    }
}
cuando hacemos referencia a this.message estamos apuntando a la propiedad message en el contexto del objeto myObject. Por lo tanto this es una referencia al objeto en el que estamos.
Si hacemos:

myObject.talk();

veremos el mensaje:

"I say: Hello"

Parece fácil, pero se complica y mucho.

El contexto de un callback

Supongamos que queremos utilizar el método myObject.talk() como callback o event handler que se ejecuta al pulsar un botón:
$(".button1").click( myObject.talk );
Al hacer click en el botón veremos:

"I say: undefined"

¡¿Undefined?!. Cuando se llama al método myObject.talk al hacer click en el botón, el valor de this.message es undefined. La razón es que this ya no apunta al objeto myObject, hace referencia a otro contexto diferente.

En este caso, al ser un callback de un evento del DOM, el navegador asocia this al elemento que provoca el evento ( si usamos addEventListener,  que es la opción que utiliza jQuery si el navegador lo permite ). Es decir, this es el objeto button. Como dentro de este objeto no hay ninguna propiedad que se llame message, this.message es undefined.

No es el único caso en que se cambia el contexto. Si lo usamos como callback de una llamada AJAX o de setTimeout() tampoco tendrá el valor original.

Y por si las cosas no fueran ya bastante complicadas, resulta que si ponemos el método dentro de una función anónima, el mensaje es correcto:

$(".button1").click(function() {
    myObject.talk();
});


El resultado es:

"I say: Hello!"

Es casi igual que lo que teníamos antes, llamámos al método myObject.talk() pero ahora dentro de una función ¿por qué ahora sí funciona?. Bueno, el valor de this ahora sí es el objeto myObject. Lo explicaremos un poco más adelante.

Vamos a ver como se comporta el puntero this en funciones/métodos y después volveremos sobre este ejemplo para explicar qué es lo que está pasando.

this en funciones y métodos

Cuando tenemos una referencia a this dentro de una función, su valor dependerá siempre de cómo se llama a la función. 

Por ejemplo, si hacemos:
var f1 = myObject.talk;
f1();  //this.message is undefined
Estamos llamando a la función talk() de una forma diferente, desde fuera del objeto. En este caso this será el contexto desde el que ejecutamos f1(), que es el objeto global (window).

 Sin embargo, si ejecutamos la función como un método de un objeto concreto, el valor de this será ese objeto:

myObject.talk(); //this.message is "Hello!"

Podríamos incluso llamar a la misma función desde otro objeto diferente:

var Object2 = {
  message: "I'm OBJECT2",
}

Object2.talk = myObject.talk;

Object2.talk(); //this.message is "I'm OBJECT2"

En el alert en pantalla veremos:

"I say: I'm OBJECT2"

Volviendo al ejemplo del callback

Ahora podemos entender qué estaba pasando en nuestros dos ejemplos iniciales. Recordemos que teníamos la función talk() como callback de dos formas diferentes:
//Example 1
$(".button1").click( myObject.talk );

//Example 2
$(".button1").click(function() {
    myObject.talk();
});


En el primer caso le estamos pasando una referencia directa a nuestra función, para que la ejecute cuando y como quiera. Como hemos visto antes, el valor de this dependerá de cómo se llame a la función. Nosotros ya no tenemos el control sobre cómo se la llamará, por eso el valor del contexto ya no es nuestro objeto.

El navegador, cuando llame al callback, colocará el contexto del objeto del DOM que ha lanzado el evento.

En el segundo caso se llama a la función anónima también con el contexto del elemento button, pero dentro de esta función estamos ejecutando explícitamente un método del objeto myObject:


myObject.talk();

Es una llamada directa al método dentro del objeto concreto y, como hemos visto antes, el contexto será el del objeto que se referencia al llamarlo. Por eso aquí el puntero this es correcto.

Hay varias formas de asegurarnos de que el contexto con el que se llama a una función es el que nosotros queremos, como Function.apply(), Function.call() o utilizando una función proxy, pero esto lo veremos en otro post.

 Fuentes:
  MDN This


viernes, 21 de septiembre de 2012

¿Que es el HTML5 Shiv?

HTML5 Shiv (o Shim) es un hack necesario para poder utilizar los nuevos elementos semánticos de HTML5 (header, footer, article, etc ) en IE8 y anteriores. Vamos a ver en detalle cual es el problema y cómo se soluciona.

Internet Explorer ignora las nuevas etiquetas

El problema es que al utilizar las nuevas etiquetas para estructurar una página web, la presentación final va a depender totalmente de los estilos que apliquemos a esas etiquetas. El Internet Explorer ( versión 8 y anteriores ) no aplica los estilos a las etiquetas nuevas porque no las reconoce. Ignora las etiquetas y los estilos asociados.

Si hemos creado un documento utilizando, por ejemplo:

My Great Web Header

y los correspondientes estilos en CSS:

header {
    border: 1px solid black;
    background-color: wheat;
    font-size: 160%;
    padding: 10px;
}

Lo que esperamos ver es algo así:

Lo que verán los visitantes que utilicen el IE8 o inferior es:

Esto evidentemente nos obliga a buscar alguna solución si tenemos que dar soporte a estos navegadores y queremos utilizar las etiquetas nuevas. Esta solución es lo que se ha llamado HTML5 Shiv.

La solución para que IE aplique CSS

Afortunadamente alguien descubrió un hack muy sencillo que permite aplicar reglas CSS a elementos que el IE no reconoce. Sólo tenemos que crear explícitamente el elemento con document.createElement( elemento ).

Es decir, que bastaría con crear los elementos que vayamos a usar en la cabecera de nuestro documento:

<!DOCTYPE html>
<html lang="es">
  <head>
     
     Ejemplo de HTML5 Shiv
     
  </head>
  ...

Como es un código de uso habitual y que sólamente necesitamos ejecutar cuando la página se cargue en un IE6, IE7 o IE8, lo mejor es separarlo en un fichero externo y cargarlo de forma condicional para IE<9 :





Es necesario colocarlo en la cabecera porque el Internet Explorer necesita reconocer los elementos para poder presentarlos.

Hay que tener en cuenta también que, por defecto, estos elementos nuevos para el IE van a ser elementos de línea. Si queremos que sean elementos de bloque ( en la mayoría de los casos es lo normal ) tenemos que darles este estilo explícitamente en nuestro CSS:


header,nav,article,footer,
section,aside,figure,figcaption{display:block}

Utiliza el script ‘oficial’

El script html5shiv.js mantenido en GitHub por Alexander Farkas se ha convertido en el estándar de facto para utilizar los elementos de HTML5 en Internet Explorer anteriores al IE9.

Es recomendable utilizarlo porque soluciona otros problemas relacionados que se han ido encontrando con el tiempo ( problemas al imprimir y al manejar dinámicamente las etiquetas con innerHTML ).

Si sólamente necesitas dar estilo a los elementos, el código que hemos descrito antes puede servirte, pero utilizando el script html5shiv.js sabes que tienes cubiertos todos los problemas relacionados que se han encontrado hasta ahora y que ha sido probado por muchos miles de personas. Estas son las ventajas del software libre y de la colaboración.

Nota: Este script viene también incluido en la popular librería Modernizr, no es necesario incluirlo a parte.

Fuentes:
HTML5 Shiv
The Story of the HTML5 Shiv

miércoles, 19 de septiembre de 2012

Compresión HTTP

En un post anterior hablaba de que habilitar la compresión HTTP es una acción sencilla que puede mejorar enormemente el rendimiento de nuestra web. El funcionamiento está especificado en el estándar HTTP1.1 y forma parte del protocolo que implementan los navegadores y los servidores.

El concepto es muy sencillo: el servidor comprime automáticamente los datos antes de enviarlos y el navegador los descomprime cuando los recibe. El proceso de compresión y descompresión es rapidísimo. Nos permite ahorrar ancho de banda y mejorar los tiempos de respuesta al transmitir menos datos.

¿Y si algún navegador no soporta la compresión?

Todos los navegadores modernos lo soportan (incluso IE6), pero también existen otro tipo de clientes que pueden no implementarlo, como algunos robots de búsqueda o algunos browsers muy simplificados. En cualquier caso esto no supone ningún problema. Los navegadores que no soportan la compresión HTTP van a recibir los ficheros sin comprimir.

Cada vez que el navegador hace una petición al servidor, le indica, mediante una cabecera HTTP, que puede recibir contenido comprimido. El servidor, si está configurado para comprimir los datos, los enviará comprimidos y con una cabecera indicando el tipo de compresión aplicada.
Si el navegador no indica que puede manejarlo, el servidor nunca va a comprimir los ficheros
Este sería un ejemplo (simplificado) de la petición HTTP del cliente:

GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate

Vemos como el navegador indica al servidor que acepta contenido comprimido y le pasa una lista de formatos que puede descomprimir. En este caso gzip y deflate.

El servidor, en su respuesta con el contenido, incluirá las siguientes cabeceras (entre otras):

HTTP/1.1 200 OK
Date: Wed, 19 Sep 2012 13:42:14 GMT
Server: Apache/2.0
Content-Encoding: gzip
Content-Length: 1285

Indica al navegador que el contenido ha sido comprimido con gzip. Si la compresión HTTP no estuviera habilitada no incluiría la cabecera Content-Encoding y lo enviaría sin comprimir. El navegador, al no recibir la cabecera, sabría que no necesita descomprimir los ficheros.

No todos los ficheros se deben comprimir

El servidor web requiere una configuración para poder comprimir el contenido. Ademas de habilitar la funcionalidad debemos tambien indicar qué tipo de ficheros ( MIME type ) deben comprimirse, porque no todos lo necesitan.

Normalmente se configura sólo para ficheros de tipo texto ( JS, CSS, HTML, JSON, etc ). Las imágenes (JPG, PNG ) y el contenido multimedia ya están en formatos comprimidos y no ganamos nada comprimiendolos de nuevo. Estaríamos añadiendo carga de procesador tanto en el servidor como en el cliente sin ningún beneficio.


domingo, 16 de septiembre de 2012

CSS: Algunas curiosidades de 'margin collapsing'

Lo que en CSS se llama margin collapsing consiste en que cuando los margenes verticales de dos elementos de bloque se tocan, se fusionan en uno solo, del tamaño del mayor.

No encuentro una buena traducción en español así que hablaremos de margenes que se fusionan o se combinan.

Este comportamiento hace que, por ejemplo, si definimos que los párrafos tengan un margen de 10px, queden separados exactamente 10 pixels y no 20, que sería la suma del margen inferior del primer párrafo y el margen superior del segundo.



Este es el caso más sencillo y resulta fácil de comprender, pero cuando el fusionado de márgenes ocurre con elementos anidados, da lugar a situaciones un poco más confusas.

Elementos anidados

La combinación automática de margenes también se puede dar con un elemento dentro de otro. Los margenes del elemento padre y el elemento hijo se fusionan en uno sólo.

Por ejemplo, tenemos el siguiente HTML:
Se trata símplemente de un DIV dentro de otro, cada uno con su margen. Si no hubiera combinación de márgenes, lo veríamos así:



 Como vemos en la imagen superior, los márgenes top y bottom de los dos elementos están en contacto, por lo que se van a fusionar quedando sólo un margen, el mayor de los dos ( en este caso el de 30px):

 

El DIV interior queda sin margen. El DIV exterior queda con un margen de 30px.

 Lo curioso de este caso es que aunque el DIV interior sea el que tiene el margen más grande, el margen resultante se va a aplicar al DIV exterior. Cambiemos los márgenes en el HTML:
Si los margenes no se fusionan lo veríamos así:



 Igual que antes, tenemos dos DIVs cuyos márgenes están en contacto, por lo tanto se fusionan y queda un sólo margen del valor más grande.



 La conclusión es que el margen resultante se aplica siempre al elemento más externo que participa en la combinación. El DIV naranja siempre se queda sin margen. No importa cuál sea el valor que prevalece, se aplica siempre al elemento exterior.

¿Cómo evitamos que ocurra?


Es importante resaltar que para que se produzca esta combinación de márgenes, estos se tiene que tocar. Podemos evitar que ocurra si hay algo entre ellos. Basta con poner un borde o un *padding* que los separe:



 Además, cuando los elementos de bloque tienen alguna de las siguientes características, no se produce la combinación de márgenes:

  • Elementos flotantes
  • Elementos con posición absoluta
  • Elementos inline-block
  • Elementos con overflow diferente de visible. Sus márgenes no se fusionan con elementos hijo
  • Elementos con clear


References:
CSS - Auto-height and margin-collapsing
Understand CSS margins collapsing
CSS Tutorial - Margins Collapsing
Collapsing margins

viernes, 14 de septiembre de 2012

Nunca habrá CSS4

Muy clarificadora esta entrada, del blog de un miembro del grupo de trabajo de CSS en el W3C, sobre el nuevo formato modular de los estándares de CSS y porqué no tiene sentido hablar de CSS4.

CSS2.1 fué el último estandar que definía una versión completa de CSS. El grupo se dio cuenta de que continuar con versiónes monolíticas era un proceso muy lento y muy dificil de mantener. Decidieron dividirlo en módulos independientes que evolucionan a su propio ritmo y tienen su propio número de versión ( Selectors, Colors, Backgrounds and Borders, Media Queries, etc ). Cada uno de estos módulos es o será un estándar (una recomendación, para hablar con propiedad).

CSS3 es en realidad un nombre genérico para referirse a CSS posterior a CSS2.1 pero no podemos hablar de CSS de nivel 3 como una recomendación del W3C completa. Algunos módulos han empezado en el nivel 3 (si se basan en CSS2.1), otros han empezado en el 1 y otros van ya por el 4. Todos ellos forman parte de CSS3.

miércoles, 12 de septiembre de 2012

4 puntos sencillos para hacer nuestra web más rápida

Chris Coyer explica en el vídeo "Let’s Do Simple Stuff to Make Our Websites Faster" cuatro puntos importantes y sencillos para mejorar el rendimiento de nuestra web:
  1. Activar siempre la compresión HTTP  en el servidor.
  2. Cachear siempre que sea posible, tanto en el cliente como en el servidor.
  3. Optimizar las imágenes. Hay algunas herramientas especificas para esto.
  4. Combinar nuestros JS y CSS en el menor número de ficheros posible.
Aproximadamente el 80% del retardo que percibe el usuario final es debido a tareas de front-end, por lo tanto, es muy importante prestar especial atención a la optimización de la parte cliente (fuente: Steve Souders).

Estos cuatro puntos requieren muy poco esfuerzo y pueden tener un impacto enorme en el rendimiento de nuestra aplicación web.

Con el punto 1 hacemos que el servidor comprima los ficheros antes de enviarlos. El navegador los descomprime cuando lo recibe. Este proceso es rapidísimo y, en general,  siempre es mejor habilitar esta opción en el servidor.

Por otro lado, cachear, especialmente en el cliente, es importante porque la petición/envío HTTP de ficheros es lo más lento.
"La petición HTTP más rápida es la que no se hace"
Este es también el objetivo del último, evitar peticiones HTTP innecesarias al concatenar varios ficheros javascript o css.

domingo, 9 de septiembre de 2012

CSS: Pseudo-clases y pseudo-elementos

Los pseudo-elementos y las pseudo-clases se definen para poder aplicar estilos en algunos elementos, o partes de un elemento, en casos especiales que no quedan cubiertos con los selectores habituales. Nos permiten seleccionar, por ejemplo, la primera letra de una línea, la primera línea de un párrafo, un link cuando ya ha sido pulsado, etc.
La sintaxis es la misma en los dos casos:

 a:visited {
 /*esto es una pseudo-clase*/
 }

p:first-line {
 /*esto es un pseudo-elemento*/
 }
 
CSS3 introduce una diferencia, recomienda utilizar “::” para los pseudo elementos, para diferenciarlos de las pseudo clases:

 a:visited {
 /*esto es una pseudo-clase*/
 }

p::first-line {
 /*esto es un pseudo-elemento*/
 }
 
De todas formas el mismo estándar especifica que los navegadores deben continuar soportando la notación anterior para evitar problemas de compatibilidad con código CSS antiguo, al menos para los pseudo-elementos definidos en CSS1 y CSS2 ( :first-line, :first-letter, :before and :after ).

pseudo-clases

Seleccionan un elemento que cumple alguna condición o estado determinado, por ejemplo un link cuando el ratón está encima ( a:hover) o un elemento cuando es el primero dentro del elemento padre ( li:first-child). Podría ser similar a aplicar una clase temporal a un elemento en un estado determinado.

Pseudo-clases especificas para enlaces :link y :visited

 
 a:link { color: blue; }
 
Aplica a todos los enlaces que todavía no han sido visitados. Aparecerían en color azul.
 
 a:visited { color: red; }
 
Aplica a los enlaces que ya han sido visitados. Aparecerían en color rojo.

Pseudo-clases dinámicas :hover, :active, :focus

Permiten cambiar el estilo en respuesta a alguna acción del usuario. Se utilizan a menudo en los enlaces, como las anteriores, pero pueden utilizarse en otros elementos.
 
 button:hover { color: green; }
 
Define un estilo que se muestra mientras el cursos está sobre el elemento. Es muy utilizado en enlaces y botones para hacer un ligero cambio de estilo que muestra claramente sobre que opción del menú nos encontramos.
 
 a:active { color: orange; }
 
Define un estilo diferente durante el momento en el que el elemento está siendo pulsado.
input:focus{ background-color:yellow; }
Se aplica a un elemento cuando recibe el foco.

:first-child

Selecciona un elemento sólo cuando es el primer hijo del elemento padre. Por ejemplo, la siguiente declaración:
p:first-child { background:yellow; }

Se aplicará a todos los párrafos que sean el primer elemento dentro de un contenedor. Si tenemos varios divs para dividir la página en columnas, los primeros párrafos de cada columna aparecerán en amarillo.

Para hacer que el primer elemento de una lista aparezca siempre en rojo:
li:first-child{ color: red; }

Lenguaje :lang

CSS nos permite seleccionar un elemento basandonos en el lenguaje que se ha definido mediante el atributo lang ( lang=“es”). Si tuvieramos dos párrafos definidos de la siguiente forma:
 

Esto es una casa

This is a house

Podríamos seleccionarlos y darles estilos diferentes:
 
p:lang(es) { color: red; }
p:lang(en) { color: blue; }

Pseudo-classes de CSS3

El nuevo estándar añade una cantidad importante de pseudo-clases nuevas a las que dedicaré un post en detalle más adelante ( :target, :root, :empty, :only-child, :only-of-type, :last-child, :nth-child, etc).

pseudo-elementos

Permiten seleccionar ciertos ‘elementos virtuales’ que no existen como tales en el DOM, por ejemplo la primera linea de texto en un párrafo ( p:first-line) o la primera letra de una línea (p:first-letter).

:first-line y :first-letter

Permiten seleccionar la primera línea de un elemento y la primera letra de un elemento, respectivamente.

p:first-line { color: gray; }
p:first-letter { text-transform: uppercase; }
Estas dos reglas CSS harían que la primera línea de cada párrafo fuera siempre de color gris y que la primera letra de cada párrafo aparezca en mayúsculas.

Estos dos pseudo-elementos sólo pueden aplicarse a elementos de bloque y a celdas de una tabla. No pueden aplicarse a un span, por ejemplo.

:before y :after

Se utilizan para insertar contenido, generado en nuestro CSS, al principio o al final de un elemento existente. Podemos también definir el estilo de este nuevo contenido dentro de la misma regla.

La propiedad content sirve para definir el contenido nuevo que se añade.

Por ejemplo, si tenemos una lista de artículos, podriamos utilizar el siguiente código CSS para colocar una indicación de NEW delante y detras de los elementos nuevos:

 
li.new:before {
    content: "NEW - ";
    background-color: green;
}

li.new:after {
    content: " - NEW";
    background-color: green;
}

Todos los elementos de la lista a los que coloquemos la clase “new” apareceran con ese texto extra para llamar la atención.

Cuidado al colocarlos separados por comas

Al utilizar estos elementos en nuestra hoja de estilos conviene tener en cuenta que, si los colocamos en una regla de CSS junto con otros selectores, separados por comas, el navegador que no soporte el pseudo-elemento ignorará los siguientes elementos de la lista, sin aplicarles el estilo.

En el siguiente ejemplo:

p:last-child, p.special {
 /* mis estilos */
}

IE8 no soporta last-child ( de CSS3) por lo que salta al final de la lista y tampoco aplica el estilo a los párrafos p.special.

References:
W3C CSS2 Pseudo-elements and pseudo-classes

miércoles, 5 de septiembre de 2012

CSS: Para qué sirve vertical-align

Probablemente todos hemos intentado alguna vez utilizar vertical-align para centrar verticalmente un contenido ( por ejemplo, un texto ) dentro de un div.


En la imagen anterior, aunque apliquemos vertical-align: middle al div contenedor o al bloque de texto, no va cambiar nada. En los dos casos el navegador ignora esta propiedad porque no aplica a esta situación.

Entonces, ¿para qué sirve vertical-align?

Esta propiedad se define en el estándar CSS2 del W3C como aplicable sólo a elementos inline y table-cell. Esta es la razón por la que no funciona para centrar el ejemplo anterior, porque no puede aplicarse a elementos de bloque.

Además, por si el nombre no era ya bastante confuso, la propiedad tiene diferente significado según se aplique a elementos inline o a elementos con display: table-cell, por lo que veremos los dos casos por separado.

Los valores que puede tomar son:
Valores: baseline | top |  bottom | text-top | text-bottom | middle | sub | super | <porcentaje %> | <longitud (px, cm, etc)> | inherit

vertical-align para elementos inline

Sirve para establecer la posición vertical de un elemento con respecto a la línea en la que se encuentra.
En la siguiente figura podemos ver 6 líneas imaginarias que son importanes para entender lo que significa cada uno de los valores de esta propiedad:


Las líneas top y bottom quedan siempre por encima y por debajo, respectivamente, de todo el contenido. Si no hay imágenes, estas líneas pueden coincidir con text-top y text-bottom.

Las líneas text-top y text-bottom quedan por encima y por debajo, respectivamente, del punto más alto (o bajo) del texto, incluyendo símbolos y acentos.

La línea middle se coloca justo en el centro de la altura de las letras minúsculas (se toma la letra ‘x’ como referencia).

Baseline es la línea sobre la que se apoya el texto.

En los siguientes ejemplos veremos cómo se alinea una imagen y un texto (en gris) con respecto a una línea de texto sin modificar (en negro), al cambiarles el vertical-align.

El HTML sería este:
 
  This is the default linedefault alignment 
  


Al elemento <p> le pondremos un borde azul, a los dos elementos que modificaremos con el vertical-align les ponemos borde naranja y al texto por defecto del párrafo, un borde gris.

Sin ningún vertical-align, se vería así:


Para cada uno de los valores posibles de la propiedad tendríamos:

baseline


Este caso es igual a la imagen sin vertical-align que veíamoss arriba, porque este es el valor que se muestra por defecto. La baseline del texto en gris y la base de la imagen se alinean con la baseline del texto sin modificar del elemento padre ( el <p>).


top


La parte superior de los elementos se alinean respecto al punto más alto de la línea ( sea imagen o texto ).

 

bottom


La parte inferior de los elementos se alinean respecto al punto más bajo de la línea ( sea imagen o texto ).

text-top


Los elementos se alinean con el punto más alto del texto. Es importante tener en cuenta que quizá la letra que tiene el punto más alto en nuestra fuente no aparece en nuestro texto. Aun así, la colocación se realiza respecto a ese punto más alto. Las letras más altas normalmente son las mayúsculas acentuadas.


text-bottom


Caso apuesto a text-top, los elementos se alinean con la parte más baja del texto. No es lo mismo que baseline porque en este caso vamos más abajo, incluyendo las líneas que bajan por debajo de la línea de base ( letras como p, j, g, etc).


middle


En este caso el navegador trata de alinear el centro vertical de los elementos con el centro del texto del padre ( el párrafo <p>).

 Lo que los navegadores hacen es alinear el centro vertical del elemento (de su caja) con la línea de base ( baseline ) del texto del padre más la mitad de la altura de la x. Visualmente puede no ser exactamente lo que esperamos porque, al no haber letras con partes descendentes, parece un poco descentrado.

super


Se utiliza para posicionar algo como superíndice.

sub


Se utiliza para posicionar algo como subíndice.

porcentaje


Eleva la posición del elemento en el porcentaje indicado. Es un porcentaje de la altura de la caja del propio elemento que tiene el vertical-align, por eso la imagen y el texto están a alturas diferentes. El valor puede ser negativo, en ese caso la posición baja. El valor 0% corresponde a la baseline.

Longitud en pixels o otras unidades


Eleva la posición del elemento en las unidades indicadas (px, cm, etc). El valor puede ser negativo, en ese caso la posición baja. El valor 0 corresponde a la baseline.

vertical-align para celdas de una tabla o elementos con display:table-cell

En el contexto de celdas de una tabla esta propiedad tiene un significado diferente. Sirve para definir como se alinea verticalmente el contenido de una celda. En este contexto sólo aplican los valores middle, top y bottom.

Para celdas de una tabla ( <table> ) el valor por defecto es middle, pero para un div con display:table-cell, el valor por defecto es top.

La siguiente imagen muestra como queda un bloque de texto con cada uno de los valores:


















martes, 28 de agosto de 2012

Los scripts externos pueden bloquear una web

Cuando cargamos un fichero JavaScript externo mediante una etiqueta <script> incluida en nuestro HTML, estamos corriendo un riesgo importante. Ese script externo a nuestra aplicación, probablemente un banner de publicidad o un widget, puede bloquear nuestra página y dejarla en blanco durante varios minutos o incluso evitar que llegue a cargarse. Steve Souders explica el problema en su presentación “Your script just killed my site” y proporciona una demostración online.

En la siguiente imagen podemos ver al menos tres elementos que se cargan mediante javascript externo en una página web real:



Si el código que incluimos para cargar estos elementos (normalmente proporcionado también por la empresa externa) no es apropiado, puede bloquear nuestra web.

¿Porqué un simple script puede bloquear nuestra página?

Un archivo JavaScript puede cargarse de manera síncrona (bloqueante) o asíncrona (no-bloqueante). Veamos que implica cada una:


Carga síncrona (bloqueante)

Decimos que se carga de forma síncrona cuando el navegador tiene que esperar a que el archivo se haya descargado y ejecutado para continuar presentando elementos en la página. Esto ocurre cuando utilizamos una etiqueta <script> incluida en el HTML. Al encontrarla, el navegador no continua calculando y mostrando los siguientes elementos de la página, este proceso se para hasta la ejecución del fichero. Esto ocurre para todos los navegadores.

La razón por la que se bloquea es que este script, al estar incluido de forma estática en el HTML, se considera parte de la página que se está presentando y, por lo tanto, el javascript que contiene puede crear nuevos elementos en la página ( o modificarlos si se carga en el body). Se espera a ejecutarlo completo para poder presentar la página correctamente.

Carga asíncrona (no-bloqueante)

La carga es asíncrona cuando el proceso de carga se realiza sin bloquear la presentación de otros elementos de la página.

Podemos conseguir la carga no-bloqueante mediante la inserción dinámica de la etiqueta <script> o utilizando atributos como  “defer” o “async”  (HTML5). Veremos después en detalle estas opciones.

El problema con los scripts externos que pueden "matar" nuestra página, es que a veces se incluyen de forma síncrona. Si el archivo es grande va a retrasar siempre la carga de la web que lo incluye. Aunque el archivo no sea pesado, su servidor puede ser lento, estar caído, o incluso estar bloqueado ( en China, por ejemplo ). Hasta que el script no se reciba o se produzca un timeout (si lo hay) nuestra página aparecerá en blanco.


¿Qué significa exactamente no-bloqueante?


Puesto que javascript no es multi-hilo, es imposible que dos scripts se ejecuten en paralelo. Este thread es además el mismo que se encarga de renderizar los elementos de la página. Cuando hablamos de un script no-bloqueante significa que no bloquea la página (puede seguir con el renderizado de elementos del DOM) mientras se descarga, la ejecución siempre se va a realizar como tarea única, nunca en paralelo.

Tenemos entonces que el navegador tiene que realizar dos tareas con el archivo:
  1.  Descargarlo de la URL que se le indique 
  2.  Ejecutarlo
El punto 1 es normalmente el que más tiempo requiere y es el único que podemos optimizar mediante estas técnicas. La ejecución siempre va a ser bloqueante puesto que tenemos un solo hilo que se encarga de ejecutar y de presentar elementos del UI.

Formas de carga no-bloqueante


Etiqueta <script> generada dinámicamente

Lo más habitual es utilizar una etiqueta <script> que se genera dinámicamente con JavaScript. Cuando se crean de este modo, la descarga se realiza inmediatamente pero no bloquea el renderizado de otros elementos. El fichero se ejecuta cuando se ha descargado completamente:
var script = document.createElement("script");
script.type = "text/javascript";
script.src = “/path/to/script.js";
document.getElementsByTagName("head")[0].appendChild(script);
Hay que tener en cuenta que esta técnica no respeta el orden de inclusión de varios ficheros JavaScript (en algunos navegadores). No se garantiza que el fichero que se incluye primero sea el primero en ejecutarse.

Atributo ‘defer’ en <script>

Incluyendo el atributo defer la descarga comienza inmediatamente pero de forma no-bloqueante. La ejecución se retrasa hasta que la página se ha parseado completamente.


Atributo ‘async’ en <script> ( HTML5 )

En HTML5 se ha creado un atributo nuevo precisamente para este propósito:
de esta forma la etiqueta <script> se comporta igual que si la hubiéramos generado dinámicamente. El script de descarga inmediatamente sin bloquear la presentación de la página y se ejecuta en cuanto está descargado (esta es la principal diferencia respecto a defer).

Es importante señalar que todas estas técnicas retrasan el evento onload hasta que los scripts se han ejecutado. Existen otras técnicas que utilizan XHR (AJAX) o iframes, pero no aportan ventajas con respecto a las anteriores y prácticamente han sido desplazadas por estas.


Referencias:
Best way to load external javascript
Cargar JavaScript. Blocking vs non-blocking
Loading Scripts Without Blocking
What is a non-blocking script?


viernes, 24 de agosto de 2012

CSS: elementos de bloque y elementos en línea

Un elemento HTML se puede mostrar como elemento de bloque ( display: block ) o como elemento en línea ( display: inline ). Comprender exactamente la diferencia entre estos dos tipos de presentación es fundamental para un desarrollador.

Elementos de bloque ( block )

Elementos de bloque son, por ejemplo: <p>, <div>, <ul>, <li>, <form>, etc (siempre que no cambiemos su display por defecto).
Se caracterizan por:
  • Se colocan siempre en su propia línea, debajo de los elementos anteriores
  • Se expanden hasta ocupar toda la anchura disponible ( la del elemento padre )
  • La altura se ajusta al contenido
  • Pueden contener otros elementos inline o block
  • Se les puede fijar altura y anchura con CSS ( width y height )
  • Se les puede asignar margin y padding con CSS
  • Ignoran la propiedad CSS vertical-align


Elementos en línea ( inline )

Elementos en línea son, por ejemplo: <a>, <span>, <b>, <em>, etc (siempre que no cambiemos su display por defecto).
Se caracterizan por:
  • No crean una nueva línea. Se colocan ‘en línea’ con otros elementos
  • Su anchura y altura se ajusta al contenido y no podemos cambiarlas con CSS ( ignora widht y height )
  • Sólo podemos fijar margin-left y margin-right. Ignoran margin-top y margin-bottom 
  • Sólo pueden contener otros elementos inline
  • Debe respetar la propiedad white-space de CSS
  • Respeta vertical-align


Particularidades de margin y padding para elementos inline

El padding y el margin para elementos inline puede aplicarse en los cuatro lados, pero el que se define arriba y abajo no va a afectar a otros elementos, no los desplaza. Es como si ignorara estos valores, pero podemos apreciar el padding aplicado si definimos un borde o un color de fondo:


Imágenes y elementos inline-block

El elemento <img>(imagen) es un poco especial porque se comporta como una mezcla de elemento en línea y elemento de bloque:


Realmente es un elemento inline, pero de un tipo especial que admite altura y anchura. En el estándar se define como ”elemento en línea reemplazado” ( replaced inline element ) y para estos elementos width y height sí aplica.

Se define como un elemento reemplazado porque no tiene contenido por si mismo, sino que será despues reemplazado por el archivo que se obtiene de su URL.

Existe un tipo de display para hacer que cualquier elemento se comporte como lo hacen las imágenes, como un elemento de bloque que se coloca en línea:

display: inline-block

Para elementos reemplazados ( como <img> ) display: inline-block es exactamente igual al display: inline que tienen por defecto.

Referencias:
Inline elements and padding
W3C CSS2 Visual formatting model