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.