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

jueves, 29 de agosto de 2013

JavaScript: El operador 'delete'

El operador delete es un incomprendido. No sirve para borrar variables y muchas veces tratamos de utilizarlo incorrectamente. Hay dos claves muy importantes:

  • delete sirve para borrar propiedades de un objeto
  • No puedes borrar con delete ninguna variable declarada con var

La forma de usarlo es:

delete object.property

Por ejemplo:


var myObj = {
    uno: 1,
    dos: 2
};
delete myObj.uno;
console.log( myObj.uno ); // undefined

La propiedad borrada deja de existir, ya no aparecerá en las iteraciones.

delete sólo funciona sobre propiedades de objetos. No hace nada aplicado sobre variables o funciones:

var test = "no puedes borrarme";
delete test;
console.log( test ); // "no puedes borrarme"

Nota: eval(), utilizado por firebug y otras consolas de navegador, modifica la forma en la que se crean las variables, afectando al funcionamiento de delete. Estos ejemplos no pueden probarse correctamente en la consola. Es necesario crear un script para evaluarlos en una página real cargada en el navegador.

Las variables globales declaradas sin var sí pueden borrarse. Sería equivalente crearlas como una propiedad del objeto global window:


test = "Sí puedes borrarme, soy window.test";
console.log(test);           //"Sí puedes borrarme, soy window.test"
console.log(window.test);    //"Sí puedes borrarme, soy window.test"
delete test;
console.log(test);           // ReferenceError: test is not defined

Como vimos antes, esta misma variable global declarada con var NO puede borrarse.

Delete devuelve un boolean

El operador devuelve true si la propiedad referenciada ya no existe y false si aún existe. Es muy importante tener en cuenta que el valor devuelto no indica exactamente si la operación ha tenido éxito o no, sino si la propiedad referenciada existe ahora o no. Si intentamos borrar una propiedad que no existía obtenemos true.


var test1 = "no puedes borrarme";
test2 = "sí puedes borrarme";
var myObj = {
    uno: 1,
    dos: 2
};    

delete test1;        // false    
delete test2;        // true
delete test3;        // true!!. test3 no existía    
delete myObj.uno;    // true
delete myObj.cuatro; // true!!. 'myObj.cuatro' no existía

Los argumentos de una función no se borran

Los argumentos de una función son equivalentes a variables declaradas dentro de la función mediante var, por lo tanto, no podemos borrarlos:

function suma (arg1, arg2) {
    delete arg1; //false. No hace nada
    //...
}  

Hay propiedades 'especiales' que no pueden borrarse

La mayoría de las propiedades de los objetos predefinidos de JavaScript, como Array, Object, Math, etc, están marcadas con un flag especial que impide borrarlas (el atributo DontDelete). Esto impide, por ejemplo, borrar .length en un array:

delete [].length; // false
delete Math.PI;   // false

Propiedades heredadas del prototipo

No podemos borrar directamente una propiedad heredada. Tenemos que borrarla desde su objeto original (el prototipo):


function MyConstructor(){}
MyConstructor.prototype.test = "Hello";
var myObject = new MyConstructor();
delete myObject.test;                 // no hace nada
console.log(myObject.test);           // "Hello"
delete MyConstructor.prototype.test;  // borra la propiedad en el prototipo
console.log(myObject.test);           // "undefined"

Comportamiento en ES5

ECMAScript 5th edition introduce algunas novedades. Básicamente nuevos tipos de error para notificar el uso inapropiado:

  • Intentar borrar una variable, un argumento de función o una función provoca un SyntaxError
  • Borrar una variable inexistente provoca un SyntaxError

(function (arg1) {

    "use strict"; // activar modo estricto (ES5) 

    var test;
    function sum(){}

    delete arg1;  // SyntaxError (when deleting argument)
    delete test;  // SyntaxError (when deleting variable)
    delete sum;   // SyntaxError (when deleting variable created with function declaration)
    delete i_dont_exist; // SyntaxError

  })();

Fuentes:
Perfection Kills: Understanding delete
You Can’t Delete With Delete
StackOverflow: deleting objects in javascript
MDN: delete

lunes, 26 de agosto de 2013

JavaScript: diferencia entre expresión y sentencia ( expression and statement)

En JavaScript es importante conocer esta sutil diferencia porque es la causa de algunos comportamientos "especiales".

-Una expresión siempre devuelve un valor como resultado
-Una sentencia realiza alguna acción

Ejemplos clásicos de sentencias son las instrucciones 'if', 'for', 'switch' o 'while':


if ( limit > 3) {
    limit = 0;
}


for ( var i=0; i<10; i++ ) {
    // do something here
}

Todos los siguientes ejemplos son expresiones, todos devuelven un valor:


a = 3      // es una asignación pero también devuelve el valor 3
3 + 2      // devuelve 5
myCounter  // devuelve el valor de la variable 'myCounter'
"abc"      // devuelve el string "abc"
sum(a,b)   // devuelve la suma de a y b
(limit > 3) ? 0 : 1;   // devuelve 0 ó 1, según condición 

Cuando creamos un objeto con la notación literal, estamos realmente utilizando una expresión que devuelve un objeto:

{ color: "red" }    // esto es una expresión que devuelve un objeto
[1, 2, 3]           // expresión que devuelve un array  
"abc"               // expresión que devuelve un string
function () { }     // function anónima. Expresión que devuelve una función

En JavaScript podemos utilizar una expresión en cualquier punto donde se espere un valor, por ejemplo como argumento de una función o en un switch:

//como argumento
myFunction( a+3, b);

//en un switch
switch (expression) {
  case label1:
    //..
    break
  case label2:
    //..
    break;
  default:
    //..
}

JavaScript acepta también una expresión en cualquier punto donde se espera una sentencia. Lo contrario no es cierto, no podemos escribir una sentencia donde se espera una expresión. Por ejemplo, no podemos poner un 'if' como argumento de una función.

declaración de función vs expresión de función

Una función puede crearse mediate una sentencia (declaración) o mediante una expresión. Lo siguiente es una declaración de función:

function suma (a, b) {
  return a+b;
}

Como sentencia, realiza una acción, crea una variable suma que contiene la función.

Una expresión devolverá un valor (una función) que podemos asignar a una variable:

var suma = function ( a, b ) {
               return a+b;
            }

También podemos tener una expresión que crea una función con nombre (named function expression):

var sumVar = function suma ( a, b ) {
               return a+b;
            };

En este caso la parte a la derecha de la función es exactamente igual que una declaración de función, pero no es una declaración, es una expresión. ¿Como sabemos entonces si es una declaración (sentencia) o una expresión? por el contexto en el que aparece.

Cuando el parser de JavaScript encuentra la palabra reservada function al principio de una instrucción, lo toma como una declaración de función. Cuando la encuentra formando parte de una sentencia (o de una expresión actuando como sentencia), lo considera una expresión (por ejemplo en una asignación).

Es importante el concepto de contexto de expresión (expression context) y de contexto de sentencia (statement context). Debemos tenerlo en cuenta siempre que un mismo código pueda actuar como expresión y como sentencia y debemos saber como forzar el cambio de interpretación si lo necesitamos.

Un ejemplo de cambio de contexto son las funciones autoejecutables. JavaScript no permite ejecutar una declaración de función, por lo que no podemos hacer esto:

//ejemplo de error. No se puede ejecutar
function suma (a, b) {
  return a+b;
}();

Sólo una expresión de función puede ejecutarse. Para que el intérprete considere la función como una expresión tenemos que colocarla entre paréntesis. De esta forma la palabra function no se encuentra al principio y el motor de JS lo considera como una expresión a evaluar.

(function ( a, b ) {
    return a+b;
})();

Nota: '(...)' es el operador de agrupado y sólo puede contener una expresión. Por lo tanto JavaScript siempre esperará una expresión entre los paréntesis. Si intentamos colocar una sentencia dentro de los parentesis obtendremos un syntax error.

Otro caso parecido, en el que el contexto decide como se interpreta un código idéntico, es la notación literal para crear un objeto:

{ 
  color: "red" 
}

Este código puede interpretarse de dos maneras:

  1. una expresión que crea un objeto
  2. Un bloque de código ( '{ }' ) que contiene una etiqueta ('color:') y una expresión que actúa como sentencia ("red")

Igual que en el caso de la función, si el parser encuentra '{' en el inicio, sin formar parte de una sentencia o otra expresión, lo considera como el inicio de un bloque y no como una expresión; no creará el objeto. Ademas, si tenemos varias propiedades provocará un syntax error: Unexpected token:

//Syntax error
{ 
  color: "red",
  size: 14 
}

Si lo encerramos entre paréntesis o lo asignamos a una variable, el intérprete lo considera una expresión y crea el objeto. Esta es la razón por la que tenemos que añadir paréntesis a un string JSON si queremos evaluarlo con eval():

eval ("(" + myJsonString + ")" );

Fuentes:
Expressions versus statements in JavaScript
Named function expressions demystified

miércoles, 14 de agosto de 2013

Transformar saltos de línea en <br> en un textarea

Este es un problema que aparece cada vez que presentamos un área de texto en una interfaz de usuario. El usuario pulsa enter para crear líneas nuevas:

Pero cuando nosotros queremos mostrar este texto en HTML, lo que obtenemos es

El problema es que un salto de línea no se interpreta como tal en HTML. Hay varias soluciones para mostrar el texto correctamente.

Transformando saltos de línea en <br>

Lo que necesitamos es transformar los saltos de línea (invisibles en HTML) en la etiqueta <br>.

Tenemos un textarea de este tipo:


El texto que esbribe el usuario lo podemos obtener de esta forma:

//con jQuery    
var value = $('#userText').val();

//sin jQuery
var value = document.getElementById('userText').value;

Nota: No debe utilizarse .html() (de jQuery) o .innerHTML para obtener el valor de un área de texto. Devuelve el valor inicial del textarea. Cuando se escribe algo nuevo su valor será incorrecto.

El texto que obtenemos en la variable value es:

"Línea1\nLínea2\nLínea3"

Utilizamos el símbolo \n para indicar el carácter new line. Es el que se utiliza en las expresiones regulares. Es una forma sencilla para el desarrollador de introducir este carácter (ASCII: 10) en un string.

Ahora que sabemos como referenciar el carácter de nueva línea y la etiqueta que necesitamos en HTML, hacer la sustitución es sencillo:

value = value.replace(/\n/g, "<br>");

Utilizamos una expresión regular ( ver tutorial ) para sustituir cada salto de línea introducido con enter por "<br>".

En realidad, dependiendo de la plataforma y del navegador, la representación del salto de línea puede ser \n o \r\n

\r -> carriage return (retorno de carro)
\n -> Line Feed (nueva línea)

Para cubrir los dos casos la expresión regular más completa es:

value = value.replace(/\r?\n/g, "<br>");

Utilizando la etiqueta <pre>

Otra solución es no transformar el texto y mostrarlo siempre dentro de etiquetas <pre></pre>. Estas etiquetas indican texto preformateado y el navegador respeta el formato, incluyendo saltos de línea y espacios en blanco.

El HTML para mostrar el texto sería:

<pre>
Línea1
Línea2
Línea3    
</pre>    

En JavaScript podríamos mostrar el valor de la variable value sin transformarla:

var value = $('#userText').val();
$("#placeToShowText").html("<pre>" + value + "</pre>");

jueves, 25 de julio de 2013

HTTP: diferencia entre POST y PUT

El protocolo HTTP define una serie de peticiones posibles entre cliente y servidor ( GET, POST, PUT, DELETE, HEAD, etc ). POST y PUT son muy similares y hay bastante confusión respecto a su uso y sus diferencias. Las dos pueden utilizarse para crear y para actualizar un recurso, pero en situaciones diferentes.

Hasta hace unos años se utilizaba casi exclusivamente GET y POST. La interacción con el servidor era básicamente por medio de formularios HTML, que no permiten otros métodos (ni siquiera en HTML5).

En las aplicaciones actuales, la mayor parte de la comunicación con el servidor se realiza mediante AJAX, que sí permite el uso de otras peticiones HTTP. Además, la popularización de los interfaces REST implica el uso de los métodos GET, POST, PUT y DELETE para interactuar con el servidor.

En algunas páginas sobre REST encontramos la siguiente definición de los métodos (ejemplo):

  • GET para obtener un recurso del servidor
  • POST para crear un recurso del servidor
  • PUT para actualizar un recurso del servidor
  • DELETE para eliminar un recurso del servidor

Y en algunas otras encontraremos exactamente lo contrario (ejemplo):

  • GET para obtener un recurso del servidor del servidor
  • POST para actualizar un recurso del servidor
  • PUT para crear un recurso del servidor
  • DELETE para eliminar un recurso del servidor

En realidad estas definiciones son incorrectas. La diferencia no es que uno se utilice para crear y otro para actualizar. Los dos métodos pueden crear un recurso y los dos métodos pueden actualizarlo.

La diferencia no es que uno se utilice para crear y otro para actualizar, esto es incorrecto

PUT se utiliza para poner un recurso en un lugar especificado (la URL). POST es mucho más general. POST simplemente envía una información al servidor para que éste la trate como considere oportuno. Puede crear un recurso nuevo, devolver una información, puede guardarlo en la base de datos, borrarlo, duplicarlo o modificarlo, puede crear 10 recursos de un tipo y dos de otro o puede no hacer absolutamente nada.

  • PUT pone un recurso en la dirección especificada en la URL. Exactamente en esa dirección. Si no existe, lo crea, si existe lo reemplaza.
  • POST envía datos a una URL para que el recurso en esa URI los maneje.

La diferencia principal, tal como se explica en la RFC 2016 que define el protocolo HTTP 1.1, es el significado que se da a la URL de la petición:

"La diferencia fundamental entre las peticiones POST y PUT se refleja en el diferente significado de la URI de la petición. La URI en un POST identifica el recurso que manejará la entidad que se incluye (los datos). [...]. En cambio, la URI en un PUT identifica la entidad incluida con la petición -- el user agent sabe que URI debe emplearse y el servidor NO DEBE intentar aplicar la petición a un recurso diferente."

Es decir, PUT sólo debe emplearse para crear o reemplazar un recurso (que se incluye en el cuerpo de la petición) en una URL conocida. En terminos REST, conocemos la ID del objeto que vamos a crear/reemplazar y ese objeto (entidad) se incluye en el cuerpo de la petición. Por ejemplo:

PUT a la URL: myServer.com/user/1134

Crea o reemplaza el usuario con ID 1134. Exclusivamente ese. Si queremos crear un usuario nuevo, sin saber su ID, la operación correcta sería POST:

POST a la URL: myServer.com/user

El servidor crearía un nuevo objeto user y le asignaría una ID. Crearía, por ejemplo, el objeto:

myServer.com/user/1235

Esto nos lleva a otro concepto importante: idempotencia. PUT es idempotente, POST no lo es.

Idempotente significa que produce el mismo resultado sin importar cuantas veces se realice la operación.

En el ejemplo que veíamos antes, con el método PUT:

PUT a la URL: myServer.com/user/1134

Estamos definiendo una operación idempotente sobre un recurso concreto. No importa cuantas veces la lancemos, el resultado será siempre el mismo: el recurso identificado como "myServer.com/user/1134" existirá con los datos que se han pasado en la petición. Si no existía se creará. Si ya existía se reemplazará de nuevo. No afecta a nada más.

La operación POST que veíamos antes no es idempotente. Cada vez que se lance la petición al servidor creará un recurso nuevo, asignandole una nueva ID.

POST a la URL: myServer.com/user

Despues de 4 peticiones tendriamos los recursos:

  • myServer.com/user/1235
  • myServer.com/user/1236
  • myServer.com/user/1237
  • myServer.com/user/1238

Resumen

Estos son los puntos más importantes que debemos tener en cuenta:

  • Una petición PUT a una URL afecta únicamente a esa URL. Un POST a una URL puede tener cualquier efecto.
  • En una petición PUT, los datos incluidos en el cuerpo de la petición se toman como una entidad que quedará accesible en la dirección URL de la petición.
  • Solo debemos utilizar PUT cuando sabemos exactamente la url correspondiente al recurso que vamos a crear (o actualizar).
  • PUT es idempotente, POST no es idempotente.

Fuentes:
StackOverflow: PUT vs POST in REST
PUT or POST: The REST of the Story
POST vs. PUT
StackOverflow: What's the difference between a POST and a PUT HTTP REQUEST?

lunes, 22 de julio de 2013

Doble negación binaria (~~) para redondear en JavaScript

Alguna vez te puedes encontrar en JavaScript con una línea como esta:

var mins = ~~(seconds / 60);

¿Que significa el doble símbolo "~~"?¿que hace?

El operador ~

~ es un operador binario de negación o complemento (bitwise NOT operator). Este operador convierte el operando en un entero de 32 bits para luego invertir cada bit individualmente. Los ceros se convierten en unos y los unos en ceros.

El operador doble (~~) se utiliza para redondear, como un equivalente rápido de Math.floor(). Al invertir los bits dos veces quedan igual que antes, pero la conversión a entero permanece (utiliza el método interno ToInt32).

~~5.7;       // => 5 
~~32.18897;  // => 32
~~5.7e1;     // => 57  (podemos utilizar notación exponencial)
~~314e-2;    // => 3

Lo que hace realmente la doble negación binaria es eliminar cualquier número despues de la coma (truncar, más que redondear). Para los número positivos esto es equivalente a Math.floor(), pero para los números negativos no. Para los negativos es equivalente a Math.ceil(), ya que redondea hacia cero:

~~-5.7;                // => -5
Math.floor(-5.7)       // => -6 
Math.ceil(-5.7)        // => -5
~~-32.18897;           // => -32
Math.floor(-32.18897)  // => -33
Math.ceil(-32.18897)   // => -32

Mas diferencias con Math.floor()

Ademas de que el redondeo de números negativos no es igual, si el operando no es convertible a número, no nos va a devolver NAN, sino 0.

~~"abc";     // => 0 
~~null;      // => 0
~~undefined; // => 0
~~{};        // => 0
~~[];        // => 0
~~(1/0);     // => 0
~~false;     // => 0
~~true;      // => 1 //true es convertible a 1

La ganancia de rendimiento con los nuevos motores de JavaScript es mínima y la pérdida de legibilidad del código es importante. Mas que un atajo es un antipatrón, es decir, una mala forma de solucionar un problema.

Fuentes:
Double bitwise NOT (~~)
Tilde or the Floor? Practical use for JavaScript bitwise operators
Stackoverflow: What is the “double tilde” (~~) operator in JavaScript?

miércoles, 17 de julio de 2013

Diferencias entre URI y URL

Hemos escuchado y leido los dos términos miles de veces, ¿tenemos claro su significado?. En realidad son tres los términos implicados en esta enorme confusión: URI, URL y URN. El primero engloba a los otros dos:

Las definiciones son:

  • URI Uniform Resource Identifier (Identificador Uniforme de Recursos)
  • URL Uniform Resource Locator (Localizador Uniforme de Recursos)
  • URN Uniform Resource Name (Nombre Uniforme de Recursos)

Como vemos en la imagen, un identificador (URI) puede ser un localizador (URL), un nombre (URN) o ambas cosas.

Una URN es un nombre único para un recurso, pero no da ninguna información para su localización. Es también una URI, puesto que lo identifica.

Una URL también identifica un recurso (es una URI), pero su característica especial es que permite localizarlo, acceder al recurso que identifica.

Estos son algunos ejemplos de URN sacados del artículo de la wikipedia:

URN corresponde a
urn:isbn:0451450523 El libro El último unicornio, de 1968, identificado por su número ISBN.
urn:isan:0000-0000-9E59-0000-O-0000-0000-2 La película Spider-Man, de 2002, identificada por su número de identificación audiovisual (ISAN).
urn:issn:0167-6423 La revista científica Science of Computer Programming, identificada por su ISSN.
urn:ietf:rfc:2648 IETF's RFC 2648.
urn:lex:eu:council:directive:2010-03-09;2010-19-UE directiva de la Unión Europea utilizando el espacio de nombres Lex URN.

En todos los casos se proporciona una identificación única de un recurso mediante un sistema de nombrado, pero no obtenemos ninguna información respecto a la localización del documento.

Estos son algunos ejemplos de URLs:

http://www.example.com/welcome.html
https://github.com/proj/ascam.git
file:///home/user/myfile.txt

Estas direcciones identifican un recurso y además nos permiten acceder a él, indicando un protocolo y una dirección.

Es importante tener en cuenta que un recurso no tiene porqué ser un documento, una URI puede hacer referencia a un recurso abstracto, un objeto, un servicio temporal, etc.

Fuentes:

W3C: URIs, URLs, and URNs: Clarifications and Recommendations 1.0
URL vs. URI vs. URN: The Confusion Continues
URLs vs. URIs: Differences and Examples
Wikipedia: Uniform resource identifier

viernes, 12 de julio de 2013

¿Puede un ID de HTML ser un número?

¿Es correcto asignar un número como ID de un elemento en HTML?¿nos puede dar problemas?

<div id="1"> - </div>

La respuesta rápida: es correcto en HTML5 e incorrecto en las versiones anteriores. Y para la segunda pregunta, la respuesta es sí, nos puede dar problemas al tratar de asignarle estilos con CSS. Mejor no utilizarlo.

Diferencias entre HTML4.01 y HTML5

Según la especificación HTML4.01, un ID sólo puede empezar con una letra:

ID must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").

Las clases, sin embargo, no tienen esta restricción, no tienen que empezar por una letra para ser válidas.

HTML5 elimina también esta restricción para las IDs:

The value must be unique [...] and must contain at least one character. The value must not contain any space characters.

Las única limitaciones que impone para una ID es que no quede vacía y que no contenga espacios.

Válido para HTML pero no para CSS

El problema viene a la hora de intentar asignar estilos. En CSS los identificadores NO pueden empezar con un número. La siguiente definición no es válida y los estilos no se aplicarán:

#1 {
    border: 1px solid red;
}

El '1' no es un identificador válido para CSS ( CSS2.1 identifiers es la especificación referenciada por CSS3 a la hora de definir los ID válidos ):

In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F".

Con las clases tenemos el mismo problema, tampoco sería válido como identificador de clase. Ninguno de los siguientes estilos se aplicaría:

.1 {
    border: 1px solid red;
}

#33wrapper {
    background-color: blue;

}

.2box {
    margin: 0 auto;
}

Por lo tanto, pueden usarse si sólo utilizamos la ID para seleccionar el elemento con JavaScript, pero es mejor no utilizarlo si vamos a dar estilos CSS basandonos en esa ID.

Fuentes:
What are valid values for the id attribute in HTML?
CSS character escape sequences

martes, 9 de julio de 2013

Transformar segundos a horas, minutos y segundos

Normalmente, en programación, se trabaja con tiempos en segundos porque es mucho más facil operar con ellos. Sumar o restar tiempos en formato hh:mm:ss es bastante más complicado. Sin embargo, en el momento de presentar cualquier tiempo en pantalla, en el interfaz de usuario, tenemos que transformarlo al formato 'legible'.

El código no es complicado, vamos a partir de que tenemos una variable time con los segundos totales, que tenemos que pasar a otros formatos

var time = 9690;

Veamos como podriamos transformarlo.

Minutos y segundos ( mmm:ss )

En aplicaciones de video y multimedia, es habitual mostrar la duración el contenido en minutos y segundos, por ejemplo 122:33.

var minutes = Math.floor( time / 60 );
var seconds = time % 60;

//Anteponiendo un 0 a los minutos si son menos de 10 
minutes = minutes < 10 ? '0' + minutes : minutes;

//Anteponiendo un 0 a los segundos si son menos de 10 
seconds = seconds < 10 ? '0' + seconds : seconds;

var result = minutes + ":" + seconds;  // 161:30

El string ":" en la última línea nos asegura la conversión implicita a string de las variables si es necesario. El + concatenará texto en vez de sumar números.

Horas, minutos y segundos ( h:mm:ss )

Esta es la conversión mas común para presentar tiempos en pantalla.

var hours = Math.floor( time / 3600 );  
var minutes = Math.floor( (time % 3600) / 60 );
var seconds = time % 60;

//Anteponiendo un 0 a los minutos si son menos de 10 
minutes = minutes < 10 ? '0' + minutes : minutes;

//Anteponiendo un 0 a los segundos si son menos de 10 
seconds = seconds < 10 ? '0' + seconds : seconds;

var result = hours + ":" + minutes + ":" + seconds;  // 2:41:30

El código no es complicado. Lo único que hay que saber es que hay 3600 segundos en una hora.

Fuentes:
StackOverflow

jueves, 4 de julio de 2013

El problema del espacio entre elementos inline e inline-block

A veces, cuando queremos colocar elementos inline o inline-block sin ningún espacio entre ellos, aparece una 'extraña' separación de unos pixels que estropea el diseño que pretendiamos conseguir.

Podemos ver un ejemplo de este problema si queremos hacer la siguiente barra de controles de vídeo:

Los botones son imágenes simples del tipo:

Puesto que las imágenes son elementos inline, podriamos pensar que si las colocamos sin márgenes, quedarán juntas. El div con la barra de progreso (#progressBar) lo haremos inline-block en el CSS y todo debería quedar en línea y sin espacios. El CSS y HTML relevante es el siguiente:

//HTML
...
//CSS #progressBar { display: inline-block; }

No definimos ningún margen ni padding en las imágenes. Deberían quedar juntas pero aparece una separación entre ellas:

¿De donde sale ese espacio?¿cómo podemos eliminarlo?

El culpable es el salto de línea

El problema es el salto de línea entre los elementos inline. Para los navegadores es un espacio en blanco y lo deben mostrar. Si eliminamos el salto de línea en el HTML desaparece la separación:

//HTML
...
//CSS #progressBar { display: inline-block; }

Ahora no hay saltos de línea en el HTML y el diseño aparece correcto:

El problema es que el HTML puede resultar confuso e ilegible. Cuando sólo tenemos unas pocas imágenes el problema no es muy evidente. Si tenemos muchos elementos, que ademas pueden ser divs complejos, conteniendo a su vez muchas etiquetas, esta solución es inaceptable. El código resulta inmantenible.

Comentar los saltos de línea

Una solución para eliminar los saltos de línea y mantener el formato, es utilizar comentarios de HTML para enmascararlos:

//HTML
...
//CSS #progressBar { display: inline-block; }

El espacio depende del tipo y tamaño de letra

La solución/chapuza de asignar un margen negativo a los elementos para compensar el espacio, no es una buena idea. La separación es en realidad un espacio en blanco y su tamaño depende del tipo y tamaño de letra que estemos utilizando. Si cambiamos la letra cambiará la distancia entre los elementos.

No asignes un margen negativo para compensar la separación. Si cambias la letra variará el espacio entre elementos.


Reducir el tamaño de letra a cero

Si reducimos el tamaño de letra a cero, el ancho del espacio también será cero y solucionaremos el problema. Obviamente tendremos despues que redefinir el tamaño de letra adecuado en los elmentos inline o inline-block que lo necesiten.

En nuestro ejemplo vamos a suponer que dentro del div '#progressBar' tenemos que mostrar algún texto:

//HTML
...
//CSS #progressBar { display: inline-block; } #videoControls { font-size: 0; } #videoControls > div { font-size: 10px; }

En el contenedor de los elementos ("#videoControls") definimos font-size: 0 y en los elementos que sea necesario volvemos a definir el tamaño correcto font-size: 10px.

Es importante definir el tamaño en unidades absolutas. No podemos utilizar unidades relativas al valor del padre, como % o em. Una buena solución es utilizar rem ( root em ), que es relativa al valor declarado en el elemento raiz del documento (html).

//CSS
#videoControls {
   font-size: 0;
}

#videoControls > div {
   font-size: 1rem;               
}

Si queremos recuperar el tamaño de fuente para todos los hijos:

//CSS
#videoControls > * {
   font-size: 10px;               
}

NOTA: Este método no funciona en los navegadores de Android pre-Jellybean.

Utilizar float en vez de inline

Esta es la solución más común para diseños complejos. Colocamos los elementos juntos utilizando 'float: left' o 'float: right' que no presentan este problema. Por supuesto hay que conocer y tener en cuenta las particularidades de este tipo de posicionamiento.

En nuestro ejemplo el código quedaría:

//HTML
...
//CSS .right{ float: right; } .left{ float: left; } #videoControls > img, #progressBar { margin: 0; }

El resultado es perfecto:

Es importante eliminar el margen de los elementos para que queden juntos. Algunos elementos presentan valores por defecto, aunque no se los definamos.

Hemos tenido que cambiar el orden de las últimas dos imágenes en el HTML para que aparezcan como queremos. Esta es una de las particularidades del posicionamiento float de las que hablabamos antes. Hay muchas más...

martes, 25 de junio de 2013

JavaScript: Borrar elementos de un array

El operador 'delete' no borra el elemento

Si utilizamos delete con un elemento de un array:

var myArray = ["A","B","C"];
delete myArray[1];  // myArray -> ["A", undefined, "C"]

El elemento no se borra del array. Sería más exacto decir que el elemento concreto se borra (queda como undefined), pero el array no se re-indexa. Sigue teniendo los mismos indices y el mismo número de elementos.

Utiliza splice()

Para eliminar el elemento debemos utilizar el método splice() que tienen todos los arrays:

myArray.splice(index, count);

index es el índice que queremos borrar y count es el número de elementos a borrar.

var myArray = ["A","B","C"];
myArray.splice(1,1);  // myArray -> ["A", "C"]

Hemos eliminado el elemento que tenía índice 1.

No confundir con slice()

El método slice() es similar pero no modifica el array sobre el que se aplica, sino que devuelve los elementos 'extraidos':

myArray.slice(start, end);

start es el índice en el que empezamos y end el índice hasta el que 'extraemos' elementos. El elemento en la posición end no estará incluido.

var myArray = ["A","B","C"];
var result  = myArray.slice(1,2);  // myArray -> ["A", "B", "C"]
                                   // result  -> ["B"]

El array original no se modifica pero se devuelven los elementos desde el 1 hasta el 2 (no incluido).

Resumen

  • delete myArray[1] -> ["A", undefined, "C"]
  • myArray.splice(1,1) -> ["A", "C"]
  • myArray.slice(1,2) no modifica el array pero devuelve ["B"]

viernes, 21 de junio de 2013

Rollovers, precarga de imágenes y sprites

Cuando necesitamos mostrar imágenes que no están en la página inicial, la petición de la imagen y la carga tardan un tiempo que puede ser apreciable por el usuario. En algunas ocasiones este tiempo de espera no es aceptable. Por ejemplo cuando un botón cambia al pulsarlo. No es lógico que el usuario haga click y el botón reaccione medio segundo despues.

Hay varias técnicas para evitar esta espera:

  • Precargar las imágenes y cambiarlas dinámicamente
  • Crear varias imagenes dentro del elemento y ocultar/mostrar según sea necesario
  • Utilizar sprites para cargar todas las imágenes juntas

El problema

Como ejemplo vamos a trabajar con un botón tipo interruptor:

Tenemos dos imágenes separadas para representar los estados on y off del botón:

Queremos que cuando el usuario pulse el botón, la imagen cambie instantáneamente. En el evento click podemos asignar una función que sustituya una imagen por otra:

//html
//JavaScript (function () { //save img DOM element for quick access var $img = $("#switch img"); //define click handler to switch images $("#switch").click( function () { if ( $img.hasClass("on") ) { $img.attr("src", "images/switchOff.png"); } else { $img.attr("src", "images/switchOn.png"); } $img.toggleClass("on"); }); })();

En el HTML sólo creamos el elemento con la imagen inicial, con el interruptor a on (switchOn.png). Utilizaremos una clase "on" para averiguar facilmente el estado de nuestro botón.

Con el JavaScript simplemente asignamos una función que se lanzará en el evento click. La función averigua el estado actual del botón leyendo la clase, cambia la imagen a la correspondiente al estado contrario y cambia la clase para indicar el nuevo estado.

Esto funciona pero presenta un problema. La primera vez que se pulsa el botón la imagen no está cargada. Perdir y cargar la imagen lleva un tiempo y hace que la respuesta no sea inmediata. El click habrá terminado y el botón aún no ha cambiado. Cuanto peor sea la conexión o el servidor, mayor será el retraso y peor la experiencia del usuario.

La segunda vez que se pulse el botón la respuesta sí será inmediata, porque la imagen ya está cargada. Está en la caché del navegador. La solución entonces, para tener una buena respuesta inicial, sería pre-cachear la segunda imagen al cargar la página.

Precarga de imágenes

La idea es cargar las imágenes en la caché del navegador antes de que las necesitemos. Cuando las mostremos aparecerán inmediatamente.

Para pre-cachear la imagen podemos crear un objeto image en memoria:

var imgOff = new Image();
imgOff.src = "images/switchOff.png";

La primera línea crea un objeto Image y la segunda le asigna el path o URL de la imagen para que la carge. Opcionalmente podemos pasarle la altura y la anchura al constructor.

Con esto ya tendríamos la imagen disponible en la caché del navegador y su uso sería inmediato. Sólo tenemos que asignar imgOff.src al src de la imagen que estamos cambiando:

      $("#switch img").attr("src", imgOff.src);

El código completo precargando la imagen sería:

(function () {

  //preload second image
  var imgOff = new Image();
  imgOff.src = "images/switchOff.png";

  //save img DOM element for quick access
  var $img = $("#switch img");

  //define click handler to switch images
  $("#switch").click( function () { 
    if ( $img.hasClass("on") ) {
      $img.attr("src", imgOff.src);    // <-- assigned from our object
    }
    else {
      $img.attr("src", "images/switchOn.png");
    }
    $img.toggleClass("on");
  });

 })(); 

new Image() vs createElement("img")

En el ejemplo que hemos visto antes creábamos un objeto Image para cargar la imagen. En realidad este objeto es una reliquia del DOM Level 0, que es como se conoce al DOM previo a la primera estandarización (DOM level 1). Todos los navegadores proporcionan el API definido inicialmente en este DOM pre-estandar por compatibilidad.

La forma 'actual' de hacer la precarga sería:

  var imgOff = document.createElement("img");
  imgOff.src = "images/switchOff.png";

Las dos formas son equivalentes y son correctas. La que más se utiliza y la que me parece más elegante es new Image(), aunque sea parte de un API antiguo.

Precargando varias imágenes

Si tenemos varias imagenes para cachear podemos hacer:

var images = "one.png, two.png, three.png".split(",");
var tempImg = [];

for(var i=0; i < images.length; i++) {
    tempImg[i] = new Image();
    tempImg[i].src = images[x]
}

Ocultar un elemento y mostrar otro

Para hacer rollovers con sólo dos imágenes, como el ejemplo que estamos tratando, podemos crear el elemento con las dos imágenes inicialmente. Una estará oculta (switchOff.png) y la otra visible (switchOn.png). Al hacer click ocultamos On y mostramos Off. Al estar las dos imagenes en el documento desde el principio, las dos se cargarán en el inicio.

Para implementarlo crearemos una clase "hidden" con display: none. Colocaremos esa clase a la imagen que queramos ocultar:


//html
//CSS .hidden { display: none; } //JavaScript (function(){ //store all img elements for quick access var $imgs = $("#switch img"); $("#switch").click( function () { for (var i = 0; i < $imgs.length; i++) { $imgs.eq(i).toggleClass("hidden"); } }); })();

Ahora la variable $imgs contiene un array con dos imágenes. En el click recorremos las dos imagenes y cambiamos la clase 'hidden' en cada una de ellas. A la imagen que no la tiene se la ponemos para ocultarla y a la que la tenía se la quitamos para mostrarla.

La instrucción $imgs.eq(i).toggleClass("hidden"); selecciona, del array de imágenes, la que esté en el index 'i' y permuta la clase 'hidden' ( si la tiene se la quita y si no la tiene se la añade).

Utilizando Sprites

Un sprite son varias imágenes juntas en un solo fichero. Se utiliza mucho para imágenes pequeñas, tipo iconos, botones o símbolos del UI.

Este es un sprite que utiliza Google:

Es una sóla imagen .png que contiene todos los iconos que utilizan en su interfaz. Sólo se hace una petición HTTP y todas las imágenes quedan ya disponibles en la caché.

En nuestro ejemplo tenemos que construir un sprite con las dos imagenes que utilizamos:

Cuando el navegador cargue la imagen para la posición inicial del botón, ya tenemos cargada la otra (es la misma imagen). Evidentemente necesitamos alguna manera de mostrar sólo la parte que nos interesa.

Cuando la imagen que necesitamos forma parte de un sprite no podemos utilizarla en una etiqueta <img>, tenemos que colocarla siempre como imagen de fondo de un elemento (background-image). Es muy importante conocer la posición exacta de cada imagen en la composición total, así como su anchura y altura porque jugaremos con la propiedad CSS background-position para colocarla.

Como ahora el <div> del botón está vacío (no contiene una <img>), tenemos que definir su anchura y su altura en CSS para que se vea justo la parte de la imagen de fondo que nos interesa:


//html
//CSS #switch { width: 70px; height: 35px; background-image: url("images/switch_Sprite.png"); background-position: 0 35px; }

Utilizamos background-position para definir la coordenada (x, y) desde la que queremos que se muestre la imagen. En nuestro caso la imagen en off empieza en (x=0, y=0) y la imagen en on en (x=0, y=35px). Por lo que inicialmente mostraremos la imagen en background-position: 0 35px. Al hacer click cambiaremos a background-position: 0 0. A partir de la posición especificada se mostrará lo que quepa en los 70x35 pixels que hemos definido como contenedor.

En la siguiente imagen hemos colocado un borde naranja alrededor del *div* para apreciar mejor la zona donde se coloca el *background*:

El event handler que tenemos que crear en JavaScript para hacer el cambio sería:

(function(){

  var state = "on";

  $("#switch").click( function () { 
    if ( state === "on" ) {
       $("#switch").css("background-position","0 0");
       state = "off";
    }
    else {
       $("#switch").css("background-position","0 35px");
       state = "on";
    }
  });
})();

Cuando hay un click cambiamos la posición del background para que se vea el botón contrario:

Hay que tener cuidado al definir la anchura y altura del div porque si lo hacemos mal se verán más imagenes del sprite. En el siguiente CSS aumentamos la altura:

//demasiada altura, se ve el segundo botón  
#switch3 {
  border: 1px solid orange;
  width: 70px;
  height: 50px;
  background-image: url("images/switch_Sprite.png");
  background-position: 0 0;
}

El resultado es:

Al utilizar sprites es muy importante ajustar el tamaño del contenedor para que quepa justo la imagen que queremos presentar.

martes, 18 de junio de 2013

Funciones parseInt(), parseFloat(), Number() y conversion implícita

Siguiendo con el tema de la conversión de tipos que vimos en el post anterior, vamos a ver ahora en detalle las posibilidades que ofrece el lenguaje para convertir otros tipos en números.

Veremos parseInt(), parseFloat(), Number() y las formas de conversión rápida (conversión implícita), comentando las diferencias entre ellas.

parseInt() y parseFloat()

parseInt() y parseFloat() son funciones creadas para parsear un string y devolver un número si es posible. Los espacios iniciales y finales se ignoran.

JavaScript analiza la cadena para extraer las cifras que encuentre al principio. Estas cifras al principio del string son las que se transforman a tipo numérico. Cuando se encuentra el primer carácter no numerico se ignora el resto de la cadena. Si el primer carácter encontrado no es convertible a número, el resultado será NaN (Not a Number).

Cuando el valor no es un string, JavaScript hace primero una conversión implícita a string. Esta conversión, en el caso de objetos, se hace llamando al método toString(). Podemos reescribir este método para algunos objetos si nos interesa asegurarnos de que devuelvan un string convertible a número.

parseInt(string, radix) - Conversión de string a entero

radix es opcional y representa la base en la que estamos trabajando. Normalmente trabajaremos en base decimal y este será el valor tomado por defecto en los navegadores modernos (como se define en ECMAScript 5). En navegadores antiguos (IE7, IE8) los número que empiezan por "0" se consideran en base octal por defecto, a no ser que se indique explícitamente el radix 10.

El prefijo 0x indica que el número está en hexadecimal aunque no se incluya radix 16.

Algunos ejemplos:

    parseInt("10");         // 10
    parseInt("10.8");       // 10
    parseInt("10 22");      // 10 
    parseInt(" 14 ");       // 14
    parseInt("20 dias");    // 20
    parseInt("Hace 20 dias"); // NaN
    parseInt("44aa33bb");   // 44
    parseInt("3.14");       // 3
    parseInt("314e-2");     // 314
    parseInt("");           // NaN  ->  ¡¡el string vacio se convierte a NaN!!
    parseInt(null);         // NaN

    parseInt("10",10);      // 10
    parseInt("010");        // 10  ¡¡ * 8 en navegadores antiguos *  !!
    parseInt("10",8);       // 8
    parseInt("0x10");       // 16   0x indica que el número es hexadecimal
    parseInt("10",16);      //16

paseFloat(string) - Conversión de string a número en coma flotante

En este caso no existe radix. El número siempre se interpreta como decimal, independientemente de cualquier prefijo que le pongamos (0 para octal ó 0x para hexadecimal).

La diferencia con el anterior, ademas de admitir decimales, es que los números en coma flotante admiten la notación exponencial, del tipo "314e-2" o "0.0314e+2".
Si la función encuentra un carácter que no sea un número (0-9), un signo (+ o -), un punto decimal o un exponente, ignorará todos los caracteres que vengan a continuación.

Algunos ejemplos;

    parseFloat("3.14");      // 3.14
    parseFloat("314e-2");    // 3.14
    parseFloat("0.0314E+2"); // 3.14
    parseFloat("3.14dieciseis"); // 3.14
    parseFloat("A3.14");     // NaN
    parseFloat("tres");      // NaN 
    parseFloat("e-2");       // NaN
    parseFloat("0x10");      // 0     ->  No admite el prefijo 0x para indicar 'hexadecimal' 
    parseFloat("");          // NaN   ->  ¡¡el string vacio se convierte a NaN!!
    parseFloat(null);        // NaN

Esta función es muy util para convertir valores en pixels o puntos, de CSS, en valores numéricos:

parseFloat("5px");      // 5

Number()

A diferencia de los dos métodos anteriores, éste y el siguiente (conversión implícita) son especificamente para conversión de tipos. parseInt() y parseFloat() son para extraer un número de un string.

Number() es un constructor para crear objetos de tipo Number, pero cuando se utiliza sin el new funciona como un conversor a tipo númerico.

  • como constructor: var myNumber = new Number(14);
  • como método: var myNumber = Number("14");

El segundo uso, sin new, es el que nos interesa para este tema.

Puede utilizarse para números enteros o decimales y acepta también la notación exponencial.

    
Number("12");        // 12
Number("3.14");      // 3.14
Number("314e-2");    // 3.14
Number("0.0314E+2"); // 3.14
Number("e-2");       // NaN
Number('0x10');      // 16   admite el prefijo 0x para indicar 'hexadecimal'

Ignora los espacios al principio y al final, pero, diferencia de los métodos anteriores, cuando un string contiene caracteres no convertibles a números el resultado siempre es NaN, no trata de 'extraer' la parte numérica.

    
Number("12");             // 12
Number("   12 ");         // 12
Number("20 dias");        // NaN
Number("Hace 20 dias");   // NaN
Number("44aa33bb");       // NaN
Number("");               // 0    ->  ¡¡el string vacio se convierte a 0!!  
Number("       ");        // 0
Number(null);             // 0

Con Number() podemos convertir booleans en números, false siempre se convierte en 0 y true en 1.

Number(true);    // 1
Number(false);   // 0

//también podemos incluir una expresión con resultado boolean
Number( (1<2) );    // 1
Number( (1===2) );  // 0

Cuando lo utilizamos con un objeto Date, devuelve los milisegundos en Unix time (desde el 1 de enero de 1970, UTC):

Number( new Date() );    // 1371220353601

En general, cuando le pasamos un objeto llamará a .valueOf() y, si no es posible, a .toString().

Conversión implicita '+'

La conversión implícita es una forma de conversión rápida a número. Podemos utilizar cualquier operación que fuerce al intérprete a realizar una conversión implícita de tipos pero que no varíe el operando:

var myNumberValue = "8" - 0; // number 8
var myNumberValue = "8" * 1; // number 8
var myNumberValue = "8" / 1; // number 8
var myNumberValue = +"8";    // number 8

La forma más utilizada por su simplicidad es +var. El operador unitario + no cambia el valor de var pero lo convierte a número. No confundir con ++var que sí cambia el valor, sumándole uno.

Este tipo de conversión, igual que Number(), devuelve NaN si el string contiene caracteres no numéricos.

Como veremos en los siguientes ejemplos, esta forma de conversión es equivalente a Number() y devuelve los mismos resultados:

    
+"12";             // 12
+"3.14";           // 3.14
+"314e-2";         // 3.14
+"0.0314E+2";      // 3.14
+"e-2";            // NaN
+"0x10";           // 16   admite el prefijo 0x para indicar 'hexadecimal'

+"   12 ";         // 12
+"20 dias";        // NaN
+"Hace 20 dias";   // NaN
+"44aa33bb";       // NaN
+"";               // 0    ->  ¡¡el string vacio se convierte a 0!!  
+"       ";        // 0
+null;             // 0

//boolean 
+true;    // 1
+false;   // 0

//también podemos incluir una expresión con resultado boolean
+(1<2);    // 1
+(1===2);  // 0

//objetos
+( new Date() );    // 1371220353601

Conclusión

Las principales diferencias son:

  • parseInt() tiene un parámetro extra para indicar la base del número (radix).
  • parseFloat() no admite radix. Todos los números se consideran en base decimal.
  • parseInt(), Number() y '+' interpretan el prefijo '0x' como número hexadecimal, parseFloat() no.
  • parseInt() y parseFloat() pueden extraer un número al principio de un string.
  • Si el string contiene caracteres no numéricos, Number() y '+' no lo convierten, devuelven NaN.
  • Cuando el argumento es un objeto, parseInt() y parseFloat() llamarán al método .toString() antes de analizar la cadena. Number() y +var llamarán primero a .valueOf() y despues a .toString() si es necesario.
  • parseInt() no entiende la notación exponencial, todos los demás si.
  • parseInt() y parseFloat() convierten el string vacio en NaN.
  • Number() y '+' convierten el string vacio en 0.
  • parseInt() y parseFloat() de un boolean es NaN.
  • Number() y '+' de un boolean devuelven 0 para false y 1 para true.

Fuentes:

StackOverflow: Which is better, number(x) or parseFloat(x)?
StackOverflow: Side effects converting strings ...

lunes, 10 de junio de 2013

Conversión rápida de tipos en JavaScript

JavaScript puede convertir dinámicamente el valor de una variable de un tipo a otro, dependiendo del contexto. Por ejemplo, si hacemos la siguiente multiplicación:


var result = "4" * 2  // resultado 8 

En intérprete convertirá implícitamente el string "4" en un número porque entiende, por el contexto, que queremos multiplicar dos números.

A veces la conversión implícita es un problema y el resultado no es lo que queremos:


var result = "4" + 8  //resultado "48"

En este caso JavaScript considera que queremos concatenar dos strings. Convierte 8 en un string y devuelve "48". Sólo si los dos operandos son números se realizará la suma, si cualquiera de ellos es un string se concatenan.

Para todos los casos en los que la conversión automática puede darnos problemas, lo mejor es que nosotros forcemos la conversión a los tipos apropiados.

Utilizando constructores

Los tipos básicos ( boolean, string, number .... ) tienen un constructor asociado (por si queremos crearlos como objetos en vez de tipos primitivos). Podemos utilizar el constructor como una función para realizar la conversión explícita:


Number("4");     // 4
Number("Hola")   // NaN ( Not a Number)
Number("3 Hola") // NaN
Number(1e3)      // 1000 ( "e" significa exponente)

String(4);       // "4"
String(0.7);     // "0.7"

Boolean(4);      // true
Boolean(0);      // false
Boolean([1,2,3]) // true

Existen también las funciones parseInt() y parseFloat() para hacer la conversión de strings a números. Estos métodos los veremos en detalle en otra entrada.

Forma rápida de conversión de tipos

Doble negación para convertir a boolean

var myBooleanValue = !!8; // true

Concatenar un string vacío para convertir a string

var myStringValue = "" + 8; // "8"

'+' para convertir a número

Los strings son siempre convertidos en números automáticamente si actuan como operandos de una expresión matemática ( excepto para la suma, que también concatena textos ). Sabiendo esto, una forma rápida de convertir un string en un número es incluirlo en alguna operación que no modifique su valor, como restarle 0 o multiplicarlo por 1:


var myNumberValue = "8" - 0; // number 8
var myNumberValue = "8" * 1; // number 8
var myNumberValue = "8" / 1; // number 8

Sin embargo, lo que más se utiliza por su simplicidad es el "+" como operador unitario. En este caso el comportamiento de la suma sí está perfectamente definido y claro y sabemos con toda seguridad que convierte un string en un número:


var myString = "8"

var myNumberValue =  +myString  // number 8
var myNumberValue =  +"20"      // number 20

Esta conversión es la más rápida pero resulta en una notación un poco confusa. Puede confundirse con un intento erróneo de pre-incremento de una variable ( ++myVariable ). A veces se coloca entre paréntesis para intentar dejar más clara la conversión:


var myString = "8"

var myNumberValue =  (+myString)  // number 8

La conversión utilizando Number() es la más lenta de todas.

Resumiendo las opciones de conversión rápida:


//to boolean
var myBooleanValue = !!20; // true

//to string
var myStringValue = "" + 20; // "20"

//to number
var myNumberValue =  +"20"      // number 20



Fuentes:
Strings to Numbers
JavaScript, The Definitive Guide. Type Conversions
Type Conversion

viernes, 7 de junio de 2013

Detectar si una variable es un array en JavaScript

Para detectar el tipo de una variable normalmente utilizamos el operador typeof:

typeof "Hello";  //returns "string"
typeof 3;  //returns "number"

Este operador funciona bien para los tipos boolean, number, string y undefined, pero cuando la variable es un objeto ( por ejemplo un Array ) devuelve siempre "object":


typeof [];  //returns "object"
typeof {};  //returns "object"
typeof new Date(); //returns "object" 

El operador instanceof

Para conocer el tipo de objeto utilizamos el operador instanceof de la siguiente forma:

variable instanceof constructor;

De esta forma podemos preguntar si una variable es una instancia de Array, de Date, de algún constructor propio, etc.


var myArray = [];

myArray instanceof Array;  // true
myArray instanceof Number; // false
myArray instanceof String; // false
myArray instanceof Object; // true

En la última línea vemos que para un array, el operador también devuelve true en la comparación con Object. Esto es porque un array es una instancia de 'Array' y tambien de 'Object' porque el constructor Array hereda de Object.

Problemas si utilizamos multiples frames

La solución que hemos visto nos dará problemas si manejamos arrays creados en frames diferentes. Cada frame en una página tiene un entorno DOM propio y cada uno tendrá su propia clase 'Array' y 'Object'. Si intentamos hacer la comprobación con un array creado en otro frame:


arrayFromAnotherFrame instanceof Array;  // false

El resultado es false porque no es una instancia del constructor 'Array' de este frame.

El estándar de facto para detectarlo correctamente en todos los casos, propuesto por Juriy Zaytsev (Kangax), consiste en utilizar toString sobre el objeto y comprobar si devuelve "[object Array]":


function isArray(value) {
     return Object.prototype.toString.call(value) === "[object Array]";
}

ECMAScript 5 introduce Array.isArray()

La nueva versión de JavaScript introduce un método específico para detectar si un valor es un array:


var myArray = [];

Array.isArray( myArray );  // true

Soportado en IE9+, Firefox 4+, Chrome, Safari 5+ and Opera 10.5+.

Una buena solución si tenemos que dar soporte a navegadores antiguos es crear el método isArray si no existe:

if(!Array.isArray) {
  Array.isArray = function (value) {
    return Object.prototype.toString.call(value) === "[object Array]";
  };
}

Fuentes:
Perfection Kills: instanceof considered harmful