sábado, 29 de marzo de 2014

CSS Float: Tutorial Visual (I)

Float es una propiedad CSS que define un tipo de posicionamiento de los elementos de una página web. En el estándar se definen tres esquemas de posicionamiento para presentar elementos:

  1. Normal.  (y aquí se incluye también el posicionamiento relativo).
  2. Absoluto.
  3. Flotante.

El flotante es, quizá, el menos intuitivo de los tres y el que presenta particularidades más ‘extrañas’ que intentaremos aclarar completamente en esta serie de dos totorales.

Conceptos Básicos

Para que un elemento flote utilizamos la propiedad float que puede tener los siguientes valores:

  • left – Flota a la izquierda
  • right – Flota a la derecha
  • none – No flota, valor por defecto
  • inherit – Hereda el valor del padre. No se utiliza porque en IE no funciona

Como vemos, un elemento no puede ser simplemente flotante, tiene que ser flotante a la derecha o flotante a la izquierda:

#elemento1 {
    float: right;
}
#elemento2 {
    float: left;
}

Que un elemento flote significa que intentará siempre desplazarse a la izquierda o a la derecha sobre la línea actual hasta chocar con el borde de su caja contenedora o de otro elemento flotante. Sólo otro elemento float puede interponerse entre él y el borde del contenedor. Veremos muchos ejemplos en los próximos apartados para entender todas las implicaciones de este comportamiento.

Hay cuatro reglas importantes que debemos tener en cuenta. Conociéndolas podremos siempre predecir dónde quedará un elemento si lo flotamos:

Reglas de los elementos float

  1. Los elementos por encima del float no se ven afectados, no cambian su posición.
  2. El elemento float se desplaza a derecha o izquierda hasta el borde de su contenedor o hasta chocar con otro elemento flotante.
  3. Los elementos de bloque se comportan como si el float no estuviera en la página, ocupando su hueco.
  4. Los elementos de línea, cuando quedan al lado de un float acortarán su longitud si es posible, para dejarle espacio.

Veremos también que este comportamiento se puede modificar con propiedades como clear o overflow.

Flotando elementos de bloque

Vamos a empezar viendo cómo se comportan los elementos de bloque (divs en este ejemplo) cuando los flotamos. Trabajaremos con un documento con tres divs, cada uno con un fondo de color diferente.

En el HTML simplemente hemos declarado el título y tres divs vacíos dentro de un div (#wrapper) que los envuelve a todos:

 <div id="wrapper">
  <h1>Sólo Bloques (Divs)</h1>
  <div id="verde"></div>
  <div id="naranja"></div>
  <div id="azul"></div>
 </div>

Por CSS les hemos puesto un borde punteado, altura, anchura y un color de fondo diferente a cada uno para que los ejemplos queden más claros

La página se verá así en pantalla:

Si asignamos float:right al bloque naranja se desplazará a la derecha y saldrá del flujo de elementos de la página para los otros elementos de bloque, que ocuparan su lugar como si no existiera:

¿Y si lo flotamos hacia la izquierda?

¿Qué ha pasado? Lo mismo que antes, el bloque flotante (naranja) sale del flujo de elementos de la página, los bloques que están debajo ocupan su lugar como si no existiera (el azul sube). El naranja se coloca en su línea hacia la izquierda hasta el borde del contenedor tapando el bloque azul.

Para los elementos de bloque, los float están fuera del flujo de elementos de la página, no ocupan espacio

¿Y si flotamos también el bloque azul?¿qué ocurrirá?

Como siempre, los bloques que están por encima no se ven afectados, el título (h1) y el bloque verde siguen en su sitio, pero por debajo nos llama la atención que el bloque contenedor (#wrapper) se ha encogido dejando fuera los dos bloques con float:left. La razón es sencilla, para los elementos de bloque (como el div contenedor, con borde negro en la imagen), los elementos flotantes no ocupan espacio en la página, por lo tanto no los tiene en cuenta. Veremos esto en detalle mas adelante y veremos también varias formas de evitarlo.

Para saber en que orden quedan los bloques es importante saber cómo están declarados en el código HTML. En este caso el naranja está primero, flota hacia la izquierda y sále del flujo de elementos, por lo que el azul sube a su misma línea, después éste también flota hacia la izquierda por lo que se intentará colocar al lado del borde o de otros elementos flotantes anteriores. En este caso le toca detrás del naranja.

Si flotamos también el verde el resultado es:

¿Y si flotamos también el título, al fin y al cabo es otro elemento de bloque (h1)?

Como el título es el primer elemento, flota en su línea a la izquierda, los demás elementos van subiendo y flotando en su misma línea detrás de él. Cuando no hay sitio en la misma línea se desplazan a la siguiente, donde hay hueco para seguir colocándose. Y mientras tanto, el div contenedor a lo suyo, encogiendo hasta casi desaparecer.

Cuando un elemento fotado cambia de línea por falta de espacio, es muy importante tener en cuenta la altura de las cajas de cada uno, porque el elemento se quedará donde tenga un hueco y puede quedar colocado de forma extraña:

En este caso el bloque azul no cabe al lado del naranja y se desplaza a la línea siguiente pero se ha quedado ‘enganchado’ en el verde porque es unos pixels más alto que el naranja. Al cambiar de línea quedará en el primer lugar que quepa, tocando al bloque de arriba. En este caso el problema se ve muy claro, pero cuando no tenemos fondos de color y un borde marcado, los elementos quedan descuadrados y el problema no se ve fácilmente.

La siguiente situación es parecida pero ahora el bloque azul es más ancho que la distancia que queda entre el verde y el borde del contenedor, como no cabe debajo del naranja, se irá hasta el extremo:


Elementos de Línea: Imágenes y Texto

Las líneas de texto, y cualquier elemento de línea en general (span, em, strong, etc), adaptarán su longitud al colocarse al lado de un elemento flotante, para dejarle hueco.

Vamos a empezar viendo como se comportan las imágenes, que actúan como elementos de línea aunque en realidad son una mezcla, inline-block, porque fluyen en la línea pero tienen altura y anchura definible con width y height.

Veamos un documento con tres imágenes:

¿Qué ocurrirá si flotamos el pez azul hacia la izquierda?

img#pezAzul {
    float:left;
}

El pez azul se desplazará alegremente en su línea hacia la izquierda hasta el borde del contenedor (o hasta chocar con otro elemento flotante, pero en este caso no hay ninguno) y los otros elementos de línea (las otras dos imágenes), al estar al lado de un float, le harán hueco:

Si lo flotamos a la derecha:

La imagen se desplaza a la derecha hasta el borde y los otros elementos le dejan hueco.

¿Y si las imágenes estuvieran dentro de bloques, en un div? Para saber cómo van a quedar los elementos es importante saber en que orden están declarados en el código. Declaramos primero un título, luego la imagen del pez, y luego un div con borde rojo que contiene dos imágenes, la mariquita y el smiley:

 <h1>Imágenes en Divs</h1>
 <img src="images/fish1.png">
 <div class="bordeRojo">
  <img src="images/bug.png">
  <img src="images/smile.png">
 </div>

Las dos imágenes de abajo están contenidas en un div, que es un elemento de bloque y ocupa su propia línea.

Si ahora flotamos la imagen del pez hacia la izquierda:

Lo que ocurre es lo siguiente:

  1. La imagen sále del flujo normal de elementos de la página por lo que no ocupa espacio (para los elementos de bloque)
  2. El bloque de abajo (con borde rojo) sube para ocupar su espacio
  3. Los elementos de línea (las 2 imágenes) se encuentran ahora al lado de un float y se desplazan para hacerle hueco

Este comportamiento se ve mucho más claro con las líneas de texto, que también son elementos de línea (faltaría mas) y normalmente están contenidas dentro de un párrafo (<p>…</p>) que es un elemento de bloque.

En el siguiente ejemplo tenemos un documento con un título, una imagen y un párrafo:

Si flotamos la imagen a la izquierda el bloque de texto subirá como si la imagen no existiera. Las líneas quedan ahora al lado de un float y se acortarán para fluir por el lado de la imagen:

Si la flotamos a la derecha:


Terminando por hoy …

Aqui vamos a terminar la primera entrega de este tutorial, en la segunda parte veremos:

  • El uso de la propiedad clear, con numerosos ejemplos para ver cómo afecta a elementos de bloque y de línea, dependiendo de si son flotantes o no y cómo podemos utilizarlo para controlar la disposición de los elementos.
  • Porqué se produce el ‘problema’ del contenedor, que se encoge hasta desaparecer cuando contiene elementos flotantes. Veremos que esto no es un error, sino una decisión de diseño. Sabremos el porqué y varias formas diferentes de evitarlo.
  • Cómo utilizar los floats para diseñar la estructura de una página.

viernes, 7 de marzo de 2014

Atributos 'defer' y 'async' en <script>

Cuando incluimos un script en una página mediante las etiquetas <script></script>, éste se cargará de forma síncrona bloqueando el resto de la página hasta que se haya ejecutado. Ocurre lo mismo tanto si el código es inline como si es un archivo remoto. En la entrada "Los scripts externos pueden bloquear tu web" explicaba en detalle las diferencias entre carga síncrona (bloqueante) y carga asíncrona (no bloqueante).

Aunque la presentación está bloqueada, los navegadores modernos buscan otros ficheros externos que tengan que cargar y lanzan la petición (js, imágenes, css). La descarga sí se inicia, pero toda la presentación de elementos está bloqueada hasta que se ejecuten. Por esto se recomienda colocar los scripts al final, para que todo el HTML se haya presentado ya en pantalla y el usuario tenga una sensación de carga rápida.

Hay dos atributos que podemos incluir en la etiqueta <script> para modificar este comportamiento y forzar la carga asíncrona (no bloqueante):

  • defer <script defer src="myFile.js"></script>
  • async (HTML5) <script async src="myFile.js"></script>

Los dos son atributos booleanos y tienen un funcionamiento similar. La diferencia principal es el momento en que se ejecuta el código descargado. En el caso de async se ejecuta en cuanto la descarga se ha completado, mientras que en el caso de defer se ejecuta cuando se ha terminado de parsear toda la página.

Atributo 'defer'

Microsoft introdujo defer en el Internet Explorer 4 para evitar que los scripts bloquearan la presentación del resto de la página si no era necesario (si no incluian elementos nuevos).

Se utiliza de la siguiente manera:

    
<script defer src="myFile.js"></script>

Se estandarizó en HTML4 con el siguiente texto:

"defer
Este atributo booleano proporciona una indicación para el UA [browser] de que el script no va a generar ningún contenido en el documento (p.ej. el javascript no contiene document.write) y, por lo tanto, el UA puede continuar con el parseo y presentación"

En HTML5 se especifica de la siguiente forma:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...]si el atributo defer está presente [sin el atributo sync] el script se ejecuta cuando la página se ha terminado de parsear.

Es decir, este atributo se utiliza únicamente para scripts externos, se ignora para scripts inline. Algunas versiones de IE (IE<10) no respetan esta indicación del estandar y pueden aplicar defer a código en línea.

Los scripts se ejecutan justo antes de lanzarse el evento DOMContentLoaded.

Además, en otro punto del estandar se indica que los scripts con defer deben ejecutarse en el orden en que se especificaron en la página.

Atributo 'async'

async se introduce en HTML5 para indicar que el script debe cargarse de forma asíncrona, sin bloquear la presentación del resto de contenido, y ejecutarse inmediatamente cuando se haya descargado. El orden de ejecución no se garantiza.

    
<script async src="myFile.js"></script>

La especificación de HTML5 indica lo siguiente:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...] Si el atributo async está presente el script se ejecuta de forma asincrona tan pronto como esté disponible.

En este caso el evento DOMContentLoaded también esperará a que el script se ejecute.

Los dos juntos: 'async' + 'defer'

Cuando los dos atributos aparecen juntos, async prevalece sobre defer si el navegador lo soporta. defer puede añadirse como fallback para navegadores antiguos:

El atributo defer puede especificarse incluso si el atributo async está presente, para que los navegadores antiguos que sólo soportan defer utilicen esa opción en vez de la descarga síncrona bloqueante que sería la opción por defecto.

Debido a las diferentes implementaciones es mejor evitar dependencias y no asumir, en ningún caso, que los scripts se ejecutarán en orden ( con defer). Evitaremos problemas si consideramos siempre que se descargarán de forma asíncrona pero no se garantiza el orden de ejecución.

Inyectando un <script> dinámicamente

Otra opción muy usada para cargar un script de forma asíncrona es inyectar la etiqueta <script> dinámicamente, con javascript:

    

var script = document.createElement("script");
script.src = "/path/to/script.js";
document.getElementsByTagName("head")[0].appendChild(script);

Los <script> que se añaden dinámicamente al documento son asíncronos por defecto, no bloquean la presentación de la página y se ejecutan en cuanto se han descargado. El mismo comportamiento que con el atributo async.

En este caso el evento DOMContentLoaded no espera a que el script se haya ejecutado.

A veces podemos encontrar también que se incluye el atributo *async* en este tipo de scripts:

var script = document.createElement("script");
script.src = "/path/to/script.js";    
script.async = true;               //set async attribute 
document.getElementsByTagName("head")[0].appendChild(script);

Es únicamente para los navegadores Firefox 3.6 y Opera, que no tienen el comportamiento asíncrono por defecto para *scripts* inyectados pero soportarn el atributo *async*.

Fuentes:

W3C HTML5 Scripting
HTML: The Markup Language (an HTML language reference)
Deep dive into the murky waters of script loading

sábado, 1 de febrero de 2014

Atributos booleanos en HTML

En el estándar de HTML del W3C se definen algunos atributos como booleanos. Puede resultar confuso porque, a pesar de su nombre, no admiten los valores true o false. Ejemplos de estos atributos son: checked, disabled, defer, selected, etc.

En este tipo de atributos, su presencia en el elemento representa el valor true y su ausencia false. Por ejemplo, en el siguiente HTML, el primer checkbox estaría seleccionado (checked) y el segundo no:

<input type="checkbox" name="vehicle" value="car" checked>

<input type="checkbox" name="vehicle" value="plane">

Si el atributo está presente puede asignarsele un valor, aunque no es necesario. Los únicos valores admitidos son: un string vacío o un string con el mismo nombre del atributo.


Los valores true y false están especificamente prohibidos en los atributos booleanos. Para representar el valor false el atributo no debe aparecer.

En el siguiente ejemplo podemos ver varios checkbox con los atributos checked y disabled con y sin valor asignado. Todos los casos son correctos y equivalentes.

<input type="checkbox" name="vehicle" value="car" checked disabled>

<input type="checkbox" name="vehicle" value="car" checked="checked" disabled="disabled">

<input type="checkbox" name="vehicle" value="car" checked disabled="">

Atributos booleanos y javaScript

El hecho de que estos atributos no tengan un valor (o puedan no tenerlo), supone un pequeño cambio respecto a la manipulación habitual de atributos que hacemos en javaScript. No podemos modificar/leer el valor del atributo para conseguir un comportamiento u otro, como haríamos normalmente con la función .attr() de jQuery o con getAttribute()/setAttribute() de javaScript. Lo que tenemos que detectar es si el atributo está presente o no, su valor no es importante.

En realidad, para los atributos booleanos, lo que debemos leer es la propiedad equivalente del elemento en el DOM, no el atributo. JavaScript nos permite acceder a los dos. El atributo es lo que se ha indicado en el código HTML, mientras que la propiedad contiene el valor real, en cada momento, de 'atributos' y otros valores del elemento en el DOM. Las propiedades correspondientes a atributos booleanos tienen valores true o false. Esto sí son realmente booleanos.


En el caso de atributos booleanos, los atributos tendrán el valor inicial que se asignó en HTML, independientemente de que el usuario lo modifique despues (desmarcando un checkbox, por ejemplo). La propiedad siempre nos da el valor real.


Si tenemos el siguiente código HTML:

<input type=checkbox name="test" value="a" checked>A
<input type=checkbox name="test" value="b">B

Que nos mostraría lo siguiente:

En lo siguientes ejemplos podemos ver cómo leemos el valor del atributo y de la propiedad checked en jQuery y en JavaScript. Como comentario al lado, aparece el resultado que obtenemos:

//leyendo los atributos con jQuery
$("input").eq(0).attr("checked"); //checked
$("input").eq(1).attr("checked"); //undefined

//leyendo las propiedades con jQuery
$("input").eq(0).prop("checked"); //true
$("input").eq(1).prop("checked"); //false

//leyendo los atributos con javaScript
document.getElementsByTagName("input")[0].getAttribute("checked"); //""
document.getElementsByTagName("input")[1].getAttribute("checked"); //null

// ..también podemos usar .hasAttribute() en IE8+
document.getElementsByTagName("input")[0].hasAttribute("checked"); //true
document.getElementsByTagName("input")[1].hasAttribute("checked"); //false

//leyendo las propiedades con javaScript
document.getElementsByTagName("input")[0].checked; //true
document.getElementsByTagName("input")[1].checked; //false

Lo correcto es leer la propiedad, porque el atributo no refleja los cambios que puede haber hecho el usuario (depende del navegador).

Para asignar checked a un elemento mediante jQuery/javaScript podemos hacer lo siguiente:

//Asignar/eliminar la propiedad con jQuery
$("input").eq(1).prop("checked", true);
$("input").eq(1).prop("checked", false);

//Asignar/eliminar el atributo con jQuery
$("input").eq(1).attr("checked","");
$("input").eq(1).removeAttr("checked");

//Asignar/elminar la propiedad con javaScript
document.getElementsByTagName("input")[1].checked = true;
document.getElementsByTagName("input")[1].checked = false;

//Asignar/eliminar el atributo con javaScript
document.getElementsByTagName("input")[1].setAttribute("checked");
document.getElementsByTagName("input")[1].removeAttribute("checked");

Aunque cambiar el atributo con javaScript (o jQuery) sí se refleja en la propiedad correspondiente, es mejor manejar siempre la propiedad como hacemos al leerlo.

Conclusión

Lo más importante que tenemos que tener en cuenta es que el atributo y la propiedad son dos cosas diferentes que no siempre están sincronizadas. El atributo no cambia para reflejar el estado del elemento, la propiedad si.

En el caso concreto de 'checked', el atributo no se corresponde con la propiedad 'checked', sino con 'defaultChecked', que indica el valor inicial definido.

Al leer los atributos booleanos con javaScript, el atributo nos da el valor inicial, fijado en el código HTML, no responde a los cambios de estado del elemento. Debemos leer la propiedad checked.

Al modificar el estado del atributo con javaScript sí hay una sincronización atributo-propiedad, podríamos hacerlo de las dos formas pero es recomendable utilizar siempre la propiead.



sábado, 11 de enero de 2014

Tutorial de @font-face

La regla @font-face de CSS nos permite incluir en una página web tipografías diferentes de las instaladas en el PC del usuario. Es decir, nos da la libertad de incluir cualquier fuente en nuestro diseño y enviarsela al usuario como parte de la página web que está viendo.

Uso básico

Si quisieramos utilizar la fuente "shattered.ttf" que tenemos en nuestro servidor, podríamos incluirla de la siguiente forma:

@font-face {
    font-family: myFontName;    //aqui podemos usar cualquier nombre
    src: url('fonts/shattered.ttf') format("truetype");
}

Este es el uso más básico. Incluimos la fuente sólo en un formato y le damos un nombre para utilizarla en nuestra applicación. El nombre que ponemos en font-family puede ser cualquiera, no tiene que coincidir con el nombre real de la fuente. En este caso, cuando queramos usarla, la referenciamos como "myFontName".

La utilizaremos de la siguiente forma:

h1 {
    font-family: myFontName, Georgia, "Times New Roman", serif;
}

@font-face está soportado en todos los navegadores modernos (e incluso en IE6), pero no todos soportan el mismo formato de fuentes. Si queremos que nuestra letra se muestre en todos los navegadores, estamos obligados a tener los ficheros en varios formatos distintos.

Incluir varios formatos de fuente

Las incompatibilidades entre navegadores nos impiden utilizar una regla tan sencilla como la que hemos visto. Por un lado, IE<9 sólo admite el formato propio EOT, que no está soportado por ningún otro navegador. Algunos navegadores de móviles sólo admiten formato SVG, y luego tenemos los formatos TTF, OTF y WOFF. En el siguiente cuadro vemos los formatos diferentes que podemos incluir y el soporte que tienen:

@font-face file types browser support
  WOFF OTF TTF SVG EOT
IE 9 9 9 no 4
Firefox 3.6 3.5 3.5 no no
Opera 11.1 10 10 10 no
Opera Mobile 11.0 9.7 9.7 9.7 no
Safari 5.1 3.1 3.1 3.1 no
Chrome 6 4* 4* 0.3 no
Safari on iOS 5 no 4.2 3.1 no

Fuente: Stunning CSS3

Afortunadamente podemos utilizar el excelente servicio gratuito Font Squirrel para convertir nuestra letra en diferentes formatos e incluso generar el css necesario para utilizarlos.

Podemos incluir todos los formatos necesarios con la siguiente declaración, conocida como Fontspring Syntax o New Bulletproof Syntax:

@font-face {
    font-family: 'MyFontName';
    src: url('fonts/shattered.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered.woff') format('woff'),
         url('fonts/shattered.ttf')  format('truetype'),
         url('fonts/shattered.svg#svgFontName') format('svg');
}

La clave de esta declaración es un pequeño hack: incluir la interrogación despues del fichero EOT 'shattered.eot?#iefix'. La razón es que IE<9 no entiende múltiples urls separadas por comas, ni tampoco la indicación de formato format(). Considera que todo lo que aparece despues del paréntesis de apertura, hasta el último, es una URL.

Al incluir la interrogación, conseguimos que el IE entienda que la url es fonts/shattered.eot y considere el resto como parámetros de url que serán ignorados en el servidor. Es muy importante que el fichero EOT aparezca el primero.

Si queremos que IE>9 utilice el formato WOFF en vez de EOT, podemos hacer una pequeña variación cambiando el format del EOT a un valor incorrecto (el correcto es 'embedded-opentype'). De esta forma lo descartará y utilizará el siguiente que reconozca:

@font-face {
    font-family: 'MyFontName';
    src: url('fonts/shattered.eot?#iefix') format('eot'),
         url('fonts/shattered.woff') format('woff'),
         url('fonts/shattered.ttf')  format('truetype'),
         url('fonts/shattered.svg#svgFontName') format('svg');
}

Esta es la declaración más compacta, es compatible con todos los navegadores pero puede presentar algún problema con el IE9 cuando se utiliza en modo compatibilidad con IE7-8, como se explica aqui. Si nos interesa cubrir también esas opciones, podemos duplicar la src, incluyendo el fichero .eot de dos formas diferentes:

@font-face {
    font-family: 'MyFontName';
    src: url('fonts/shattered.eot'); /* IE9 Compat Modes */
    src: url('fonts/shattered.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
         url('fonts/shattered.woff') format('woff'), /* Modern Browsers */
         url('fonts/shattered.ttf')  format('truetype'), /* Safari, Android, iOS */
         url('fonts/shattered.svg#svgFontName') format('svg'); /* Legacy iOS */
    }

Cualquier navegador diferente del IE ignorará los ficheros .eot y saltará a la siguiente declaración hasta encontrar un formato que pueda usar.

Esta es una sintaxis universal, compatible con todos los navegadores en todos los casos.

Descartando fuentes locales con: local('☺')

Podemos encontrar también declaraciones incluyendo local('☺') con un smiley o cualquier otro caracter extraño:

@font-face {
    font-family: 'MyFontName';
    src: url('fonts/shattered.eot');
    src: local('☺'),
         url('fonts/shattered.woff') format('woff'),
         url('fonts/shattered.ttf')  format('truetype'),
         url('fonts/shattered.svg#svgFontName') format('svg');
}

El src: local() normalmente sirve para especificar una tipografía local (en el ordenador del usuario) que puede utilizarse sin tener que descargar ningún fichero. En este caso se incluye con un carácter especial (smiley) sólo para evitar que el navegador utilice una fuente local con el mismo nombre que la nuestra, si existe.

El problema con esto es que Android 2.2-2.3, aunque tienen soporte para @font-face, no soporta local(), con lo que nuestra declaración no funcionará para esos sistemas.

Definiendo cursiva, negrita, etc

La fuente que estamos utilizando puede tener versiones cursiva, negrita, etc:

shattered-regular.ttf
shattered-bold.ttf
shattered-italic.ttf

Para incluirlas primero tenemos que convertir todos los ficheros a los formatos necesarios. Despues tenemos dos posibilidades: incluirlas con nombre distinto (MyFontName, MyFontName-italic, etc) o incluirlas con el mismo nombre para utilizarlas con font-weight o font-style (Style Linking):

font-weight: bold;
o
font-style: italic;

La segunda solución es mejor, pero debemos tener en cuenta que no funciona en IE.

En cualquiera de los dos casos necesitaremos una declaración @font-face diferente para cada tipo.

Si las declaramos con diferente nombre:

/* Regular */
@font-face {
    font-family: 'MyFontName';
    src: url('fonts/shattered-regular.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-regular.woff') format('woff'),
         url('fonts/shattered-regular.ttf')  format('truetype'),
         url('fonts/shattered-regular.svg#svgFontName') format('svg');
}

/* Bold */
@font-face {
    font-family: 'MyFontName-bold';
    src: url('fonts/shattered-bold.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-bold.woff') format('woff'),
         url('fonts/shattered-bold.ttf')  format('truetype'),
         url('fonts/shattered-bold.svg#svgFontName') format('svg');
}

/* Italic */
@font-face {
    font-family: 'MyFontName-italic';
    src: url('fonts/shattered-italic.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-italic.woff') format('woff'),
         url('fonts/shattered-italic.ttf')  format('truetype'),
         url('fonts/shattered-italic.svg#svgFontName') format('svg');
}

Tendríamos que utilizarla de la siguiente forma:

body { font-family: MyFontName, Georgia, serif; }
h1 { font-family: 'MyFontName-bold', Georgia, serif; }
em { font-family: 'MyFontName-italic', Georgia, serif; }

Si las declaramos con el mismo nombre, utilizando Style Linking:

/* Regular */
@font-face {
    font-family: 'MyFontName';
    font-style: normal;
    font-weight: normal;
    src: url('fonts/shattered-regular.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-regular.woff') format('woff'),
         url('fonts/shattered-regular.ttf')  format('truetype'),
         url('fonts/shattered-regular.svg#svgFontName') format('svg');
}

/* Bold */
@font-face {
    font-family: 'MyFontName';
    font-style: normal;
    font-weight: bold;
    src: url('fonts/shattered-bold.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-bold.woff') format('woff'),
         url('fonts/shattered-bold.ttf')  format('truetype'),
         url('fonts/shattered-bold.svg#svgFontName') format('svg');
}

/* Italic */
@font-face {
    font-family: 'MyFontName';
    font-style: italic;
    font-weight: normal;
    src: url('fonts/shattered-italic.eot?#iefix') format('embedded-opentype'),
         url('fonts/shattered-italic.woff') format('woff'),
         url('fonts/shattered-italic.ttf')  format('truetype'),
         url('fonts/shattered-italic.svg#svgFontName') format('svg');
}

NOTA: La fuente regular tiene que estar la primera.

Definiéndolo de esta manera, podemos utilizar la fuente con el mismo nombre y definirla como negrita o italica con font-weight o font-style:

body { font-family: shattered, Georgia, serif; }
h1 { font-weight: bold; }
em { font-style: italic; }

Fuentes:
Stunning CSS3: @font-face File Types Browser Support
Paul Irish: Bulletproof @font-face Syntax
Fontspring: The New Bulletproof @Font-Face Syntax
CSS Tricks: Using @font-face
Ksesocss: @Font-face y sus problemas. Guía de uso y solución de problemas
Especificación del W3C

miércoles, 23 de octubre de 2013

Patrón Post/Redirect/Get (PRG)

Este patrón indica una forma de diseñar una aplicación web para evitar el problema del doble envío de un formulario al recargar una página. Este problema también se conoce como doble POST y es la causa de que, a veces, al recargar una página el navegador muestre el siguiente mensaje:

Mostrar una pantalla como esta, ante una acción que el usuario considera normal, es un problema grave de usabilidad. Ademas de la presentación de este mensaje, el doble POST puede duplicar en el servidor una acción que ya se había realizado, como una compra, una trasferencia, el envío de un mensaje, etc.

Vamos a ver en detalle porqué se produce este problema y la solución que propone el patrón Post/Redirect/Get

El Problema

Vamos a suponer las siguientes acciones:

  • El usuario rellena un formulario para hacer una compra.
  • Pulsa el botón 'Enviar' para realizar la compra.
  • El servidor recibe la petición, hace el cargo en la cuenta del usuario y formaliza el pedido guardandolo en la base de datos.
  • Se muestra al usuario una página de 'Compra Realizada' con un resumen de los datos de de la compra.

Este proceso es muy habitual en las aplicaciones web. La secuencia puede darse en muchos casos: una página de compra, una página para enviar un mensaje a un foro, una página para dar de alta a un usuario, etc.

La comunicación entre cliente y servidor puede verse en la siguiente imagen (sacada de Wikipedia):

En general, cuando una petición va a modificar algo en el servidor se envía mediante un mensaje POST de HTTP, como vemos en el dibujo. El problema es que si el usuario decide recargar la página (quizá no se ha mostrado bien el resumen de la compra), el navegador tiene que volver a enviar la petición que devuelve esa página. Como esa petición es un POST, implica modificar algo en el servidor, en este caso realizar de nuevo la compra. Por eso los navegadores muestran el mensaje de aviso.

Naturalmente la aplicación en el servidor puede comprobar si esta petición ya se ha realizado y evitar la duplicación, pero esto no siempre se hace. El mensaje del navegador aparecería en cualquier caso, empeorando la experiencia de usuario.

Resumiendo: Una recarga de la página puede causar la duplicación de una acción en el servidor.

La Solución

La solución que propone el patrón Post/Redirect/Get es evitar que la página que se presenta al usuario despues de la acción sea el resultado directo del POST. Se devuelve un codigo de redirección que obliga al navegador a pedir la página mediante un nuevo GET. La secuencia sería la siguiente:

En este caso, despues del POST el servidor no envía la página de 'Compra Realizada', envía una respuesta indicando una redirección, mediante las siguientes cabeceras:

HTTP/1.1 303 See Other
Location: http://comprando.com/compraRealizada

Al recibir esta respuesta el navegador automáticamente hace una nueva petición (GET) a la dirección que se indica en cabecera Location. Como respuesta a este GET, el servidor devuelve la página de 'Compra Realizada'.

Ahora, si el usuario decide recargar la página, el navegador vuelve a enviar la petición GET y la carga de nuevo. Ya no se envía el POST original y no hay peligro de modificación de datos en el servidor.

Precisamente la redirección 303 se creó para este propósito. En la especificación HTTP 1.1 podemos leer:

303 "See Other" The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource. The new URI is not a substitute reference for the originally requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

¿Que pasa si pulsamos 'Back' en el navegador?

Cuando estamos en la página de 'Compra Realizada', si pulsamos 'atras' en el navegador, iríamos a la página original del formulario, que normalmente se leerá de la cache del navegador. Esta acción no envía de nuevo el formulario.

Si despues pulsamos la flecha "adelante" en el navegador iremos de nuevo a la página de resultado de la compra, pero esta acción no envía de nuevo el formulario.

Por supuesto, si cuando estamos en la página del formulario pulsamos el botón de 'Envíar' sí se enviará de nuevo. Este caso sólo puede protegerse en la parte del servidor. Podríamos por ejemplo, crear una cookie con información de la transacción y descartarla si llega de nuevo en una rango temporal de segundos.

miércoles, 16 de octubre de 2013

JavaScript: Obtener la fecha actual en milisegundos

Tarde o temprano necesitaremos obtener la fecha actual en algún punto de nuestro programa. Si tenemos que hacer operaciones con ella, lo habitual es manejarla internamente en milisegundos (UNIX timestamp) para luego transformarla en un formato legible al presentarla en pantalla.

Tiempo Unix es un sistema para la descripción de instantes de tiempo: se define como la cantidad de segundos transcurridos desde las 00:00:00 UTC del 1 de enero de 1970.

En javascript hay dos formas equivalentes de obtener la fecha actual en tiempo UNIX:

var now1 = new Date().getTime(); //slow

var now2 = Date.now()   //fast. ECMAScript 5. Not supported for IE<9

El segundo método es múcho más rápido que el primero. Vemos a continuación los detalles de cada uno.

Método 1: new Date().getTime()

La forma 'clásica' de obtener el timestamp actual es:

var now = new Date().getTime(); 

Lo que estamos haciendo en una sóla línea es obtener un objeto Date, que por defecto se crea con la fecha y hora actual, y despues llamar a su método .getTime() que nos devuelve la fecha en milisegundos UNIX time. El resultado es un número del tipo 1381852003756.

Es una forma resumida de hacer:

var tmpDate = new Date();     //  Wed Oct 16 2013 12:37:29 GMT+0200
var now = tmpDate.getTime();  //  1381919849147

Método 2: Date.now()

Este método fue estandarizado en ECMAScript 5.

var now = Date.now()   

Es más rápido y más intuitivo que el anterior. Es importante tener en cuenta que no está soportado en IE8 ni anteriores.

La razón por la que esta solución es más rápida que la anterior es que no tiene que instanciar el objeto Date, accedemos directamente al método.


'Shim' para utilizarlo en cualquier navegador

Si queremos utilizar la versión moderna pero tenemos que dar soporte a IE8 o anteriores, podemos crear fácilmente una función que emule Date.now() en los navegadores en los que no está disponible:

if (!Date.now) {
  Date.now = function now() {
    return new Date().getTime();
  };
}

Esta función simplemente comprueba si existe el método Date.now y si no existe lo crea emulandolo mediante new Date().getTime().

miércoles, 25 de septiembre de 2013

Política del mismo origen (Same Origin Policy)

La política del mismo origen es una importante regla de seguridad que implementan todos los navegadores modernos. Su propósito es impedir que un script procedente de un sitio externo pueda acceder al DOM, datos y cookies de otra página y que pueda utilizar AJAX para realizar peticiones utilizando las cookies y credenciales que el usuario tiene activas (bancos, tiendas, correo, etc).

En otras palabras, se trata de que el navegador mantenga una separación estricta entre diferente páginas/aplicaciones. De esta forma una página cargada en un iframe o en otra pestaña del navegador no puede acceder a tu sesión de Amazon o del banco que tienes abierta en otra pestaña. También se descartan las peticiones AJAX a un origen diferente.

¿Que se considera un origen distinto?

En principio imaginamos un origen distinto como un dominio diferente. Esto es correcto, pero la definición es mucho más amplia. El origen también se considera diferente si cambia el protocolo (de http a https, por ejemplo) o el puerto desde el que se sirven los recursos:

En la siguiente tabla (de wikipedia.com) podemos ver con que orígenes se permite la comunicación si nuestra página original viene desde "http://www.example.com/dir/page.html":

URL Resultado Explicación
http://www.example.com/dir/page2.html Permitido Mismo protocolo y servidor
http://www.example.com/dir2/other.html Permitido Mismo protocolo y servidor
http://username:password@www.example.com/dir2/other.html Permitido Mismo protocolo y servidor
http://www.example.com:81/dir/other.html No Permitido Mismo protocolo y servidor pero diferente puerto
https://www.example.com/dir/other.html No Permitido Diferente protocolo
http://en.example.com/dir/other.html No Permitido Diferente servidor
http://example.com/dir/other.html No Permitido Diferente servidor
http://v2.www.example.com/dir/other.html No Permitido Diferente servidor
http://www.example.com:80/dir/other.html Evitar Puerto por defecto indicado. Depende de la implementación del navegador

¿De qué me protege la Política del mismo origen?

El objetivo es proteger al usuario que está viendo una página en su ordenador, con su navegador, de scripts maliciosos que intenten acceder a sus datos o a otros servidores utilizando sus cookies/credenciales. Si los navegadores no implementaran la SOP (Same Origin Policy) podría darse habitualmente el siguiente problema:

  • Inocencio entra en la página de su banco "misEurillos.com".
  • Inocencio abre otra pestaña para entrar en una tienda online a comprar unos caramelos para la tos: "tengoDeTodo.com".
  • La página de la tienda "tengoDeTodo.com" tiene código JavaScript para intentar robar datos de todos los clientes que entran. Mediante AJAX, hace peticiones a varios bancos conocidos (incluido misEurillos.com).
  • El banco responde con los datos de Inocencio porque la petición AJAX incluye automáticamente las cookies/credenciales que Inocencio tiene aún activas por tener sesión abierta en su banco.
  • El script de la página "tengoDeTodo.com" recibe los datos del banco y se los envía a Malone para realizar sus fechorías.

La política del mismo origen impide a los scripts externos acceder a información de la página y hacer peticiones AJAX a servidores diferentes. En este caso el script no podría enviar las peticiones AJAX a los bancos (son origenes diferentes a "tengoDeTodo.com") ni tendría acceso a las cookies o datos de las otras páginas que Inocencio está viendo.

Lo mismo es válido para el correo: si tenemos una sesión de correo abierta en una pestaña, otra página podría intentar, con sus scripts, acceder a datos de la nuestra o acceder directamente al servidor de correo para obtener nuestra información. Las cookies se envia con la petición AJAX a www.miCorreo.com, por lo que quedaremos identificados como el usuario original.

No todas las interacciones cross-site están prohibidas

Pedir recursos de un dominio diferente es algo habitual en las aplicaciones modernas y no siempre entraña peligro. Muchas páginas web cargan imágenes o contenido multimedia desde un origen diferente. Las restricciones se aplican principalmente a las peticiones que se hacen dinámicamente desde un script ( AJAX mediante XMLHttpRequest ) y a la interacción entre diferentes páginas cargadas en el navegador.

No se plantean las mismas restricciones para todos los casos de comunicación. Un script que se carga mediante la etiqueta <script> tiene acceso sin límites a toda nuestra aplicación. Una página de un dominio diferente que se pide en una etiqueta <iframe> se mostrará sin problemas, pero se limita su acceso al contenido de la página padre o de otros iframes.

En general no se limita la inclusión de contenido embebido mediante etiquetas HTML. Podemos incluir recursos cross-site de diferente tipo:

  • Código javascript con <script src="..."></script>
  • CSS con <link rel="stylesheet" href="...">
  • Imágenes con <img>
  • ficheros multimedia con <video> o <audio>
  • plug-ins con <object>, <embed> o <applet>
  • fonts con @font-face
  • cualquier contenido con <iframe>

La diferencia principal con las peticiones AJAX es que ninguna de las peticiones de la lista anterior permite leer directamente la respuesta. Si estamos en la página del banco, con sesión abierta, y enviamos una petición AJAX, recibiremos los datos bancarios (probablemente en JSON o XML). El script del banco recibe la respuesta y puede leerla. Las respuestas de las peticiones anteriores las interpreta directamente el navegador, no tenemos acceso a ellas ( hay alguna forma de conseguirlo como veremos a continuación, pero tiene sus limitaciones).

¿Por qué <SCRIPT> sí y AJAX no?

El hecho de que se pueda incluir código mediante la etiqueta <script> sin ninguna restricción resulta llamativo. Podemos cargar sin ningún problema las librerias que necesitamos desde un dominio diferente al nuestro, por ejemplo:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

La línea anterior cargará jQuery desde los servidores de Google, sin ninguna limitación. Podemos utilizar los métodos de jQuery para acceder al DOM y a nuestros datos.

El código que se incluye de esta manera queda embebido en nuestra página, el origen de estos ficheros JavaScript queda definido por el origen de la página HTML que los incluye.

Hay algunas diferencias importantes entre una petición con <script> y otra utilizando XMLHttpRequest que hacen la segunda mucho más insegura:

  • La etiqueta <script> no permite leer la información que llega, todo el código que se incluye se ejecuta inmediatamente en el contexto global ( con AJAX la respuesta se recibe como un string y es la aplicación la 'lee' el contenido y decide que hacer con la información).
  • Sólamente se pueden enviar peticiónes HTTP GET, no se puede elegir POST, PUT, DELETE, HEAD, etc.
  • No se pueden modificar las cabeceras que se envian (no se pueden incluir credenciales de seguridad HTTP AUTH).
  • No se puede acceder a la respuesta: cabeceras, código, información, etc. (con AJAX se puede acceder a las cabeceras)

<script> se utiliza a veces para conseguir una comunicación cross-site similar a AJAX, inyectandola mediante javascript en el documento cargado. Esto se conoce como JSONP. De esta manera sí se puede leer información enviada en la respuesta, pero hay una puntualización importante: la implementación de JSONP requiere que tanto el cliente como el servidor utilizen un protocolo especial. Es decir, sólo se pueden leer datos de un servidor que los exponga mediante JSONP, aceptando de esta manera el acceso público a ese recurso desde cualquier origen.

JSONP requiere que tanto el cliente como el servidor utilizen un protocolo especial. Es decir, sólo se pueden leer datos de un servidor que los exponga mediante JSONP, aceptando de esta manera el acceso público a ese recurso desde cualquier origen.

Aún así es importante que sólo se realicen peticiones a servidores propios o sobre los que exista una total confianza, porque, como comentamos antes, el script se ejecuta inmediatamente en el contexto global de la aplicación. La respuesta puede devolver una llamada a una función, tal como se espera, y despues otro código malicioso que se ejecutará también ciegamente.

Fuentes:
W3C - Same-Origin Policy
Política del mismo origen, Cross-Site Scripting (web)
MDN: Same-origin policy
IT Security: Why is the same origin policy so important?
Principles of the Same-Origin Policy draft-abarth-principles-of-origin-00
stackoverflow: Why are cross domain ajax requests labelled as a security risk
StackOverflow: Why do browser APIs restrict cross domain requests
StackOverflow: Why the cross domain ajax is a security concern