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

miércoles, 5 de junio de 2013

Tutorial de expresiones regulares en JavaScript

¿Qué son las expresiones regulares?

Una expresión regular es una forma de definir una 'plantilla', con una sintaxis propia, para localizar un patrón dentro de un texto. El ejemplo más sencillo sería definir una palabra como plantilla:

Expresión regular: /casa/

Encontraría la palabra casa en el texto:

La casa de la montaña.

La utilidad real se muestra cuando utilizamos caracteres especiales para crear patrones de busqueda mucho más complejos, del tipo:

Expresión regular: /\W*\s(\S)*$/

Parece mucho más complicado de lo que es en realidad. Sólo hay que aprender unas reglas básicas y unos cuantos caracteres especiales.

JavaScript tiene dos notaciones

En JavaScript hay dos formas de crear una expresión regular: mediante la notación literal o utilizando el constructor RegExp():


//notación literal
var myRegExp = /casa/g; /* /reg. expression/flags(g|i|m) */

//Usando el constructor RegExp
var myRegExp = new RegExp('casa','g'); /* RegExp('expression','flags(g|i|m)'); */

Las dos son equivalentes. La primera se compila cuando el script se carga y es más rápida. La segunda se compila en tiempo de ejecución por lo que es la única opción si la expresión contiene variables que tienen que ser interpoladas. En este tutorial utilizaremos la notación literal por simplicidad. Todas las explicaciones son válidas también para el constructor.

Para entender mejor el funcionamiento de las expresiones regulares conviene pensar en una busqueda caracter a caracter. Si tenemos el patrón 'casa', lo que estamos buscando no es la palabra casa, sino una 'c' seguida de una 'a' seguida de una 's', etc. Es mucho más facil entender el uso de los caracteres especiales de esta manera.

Por ejemplo, si sabemos que \s representa un espacio en blanco y que ? significa que el caracter anterior es opcional, ¿ que estamos seleccionando en la siguiente expresión regular?:

var myRegex = /\sglobos?/;

Estamos buscando un espacio en blanco, seguido de una 'g', de una 'l', de una 'o', de una 'b', de una 'o' y de una 's' que es opcional, puede aparecer o no. Por tanto seleccionaremos:

Un globo, dos globos, tres Globos.

Podemos ver que sólo ha seleccionado el primer 'globo'. Por defecto la búsqueda termina cuando se encuentra la primera coincidencia. Si queremos que siga buscando tenemos que utilizar el flag g.


Flags (g, i, m)

Los flags modifican el comportamiento por defecto de la búsqueda. Aparecen justo al lado de la barra que marca el final de la expresión regular y pueden ser:

  • g (global). Se busca siempre en el texto completo en vez de detenerse cuando encuentra la primera coincidencia con el patrón.
  • i (ignora Mayusculas). No diferencia mayúsculas y minúsculas.
  • m (multilínea). El texto incluye saltos de línea. ^ y $ se aplican a 'comienzo de línea' y 'final de línea', en vez de 'comienzo de texto' y 'final de texto'.

Si en la cadena de nuestro ejemplo queremos seleccionar también otras ocurrencias de la palabra 'globo', tenemos que utilizar:

var myRegex = /globo/g;
Un globo, dos globos, tres Globos.

La expresión encontraría dos coincidencias. El tercer 'Globo' no coincide con el patrón porque la G es mayúscula. Si añadimos el modificador i sí se seleccionará:

var myRegex = /globo/gi;
Un globo, dos globos, tres Globos.

La segunda y tercera aparición están en plural. Sería bueno que nuestra expresión regular capturara tanto globo como globos. Como ya vimos antes, podemos utilizar la ? para indicar que el carácter anterior es opcional:

var myRegex = /globos?/gi;
Un globo, dos globos, tres Globos.

?, + , *   Cuantificadores sobre el carácter anterior

Con estos tres caracteres extraños podemos indicar que el carácter inmediatamente anterior puede ser opcional, o que tiene que aparecer como mínimo una vez o que puede aparecer muchas veces.

?El carácter anterior es opcional. Aparece 0 ó 1 veces.
+El carácter anterior aparece una vez o más.
*El carácter anterior puede no aparecer, aparecer una vez o aparecer repetido muchas veces (aparece 0 o más veces).

Si tenemos el texto:

"Tengo un gloooooobo grande y tres globos pequeños."

¿Como podemos hacer para capturar los dos globos?. La expresión anterior no nos sirve porque la primera aparición quedaría fuera.

Necesitamos una expresión regular que nos seleccione cualquier patrón con una 'g' seguida de una 'l', seguida de una o más 'o', una 'b', una 'l', una 'o' y una 's' opcional.

var myRegex = /glo+bos?/gi;
Tengo un gloooooobo grande y tres globos pequeños.

En este caso, si ponemos * en vez de + también funcionaría. La diferencia es que el asterisco permite que el carácter no aparezca ( cero o más veces). Por lo tanto también seleccionaría glbo. Con + obligamos a que el carácter aparezca al menos una vez.

Para seleccionar todas las palabras en el siguiente texto:

"bo, boooo, b."

sólo nos sirve el *:

var myRegex = /bo*/g;
bo, boooo, b.

'.' Un comodín para un solo car.cter

El punto '.' es un comodín que puede sustituir a cualquier carácter excepto a un salto de línea.

La siguiente expresión:

var myRegex = /gat./gi;

capturará 'gat' seguido de cualquier carácter:

Gato, gata, gamo, gatuno, gatitos, gatoooooos, gatk, gallego, gat , gat.

En las dos últimas coincidencias podemos ver que también captura el espacio en blanco y el punto final.

{n,m} Cuantificadores más concretos

Con {n,m} podemos indicar exactamente el número de veces que aparece el carácter anterior o delimitarlo en un rango concreto ( por ejemplo que aparezca entre 3 y 5 veces).

{n}El carácter anterior aparece exactamente *n* veces.
{n,}El carácter anterior aparece *n* o más veces.
{n,m}El carácter anterior aparece un mínimo de *n* y un máximo de *m* veces

var myRegex = /r{2}/gi;
b, brr, brrr, brrrr.

En el último caso tenemos brrrr. Se seleccionan las dos primeras y despues las dos segundas.

var myRegex = /r{2,}/gi;
b, brr, brrr, brrrr.
var myRegex = /r{2,3}/gi;
b, brr, brrr, brrrr.

[abc] Conjuntos de caracteres

Para indicar que en un lugar de nuestra expresión puede aparecer cualquier de los caracteres de un conjunto concreto podemos utilizar [ ]. Por ejemplo, si queremos seleccionar 'sal', 'cal' y 'mal', podemos indicar que el primer caracter puede ser [scm]. Es decir, puede ser uno de los caracteres del conjunto:

var myRegex = /[scm]al/gi;
tal, sal, perro, mal, sota, cal, tal, salir, ramal.

También podemos negar el conjunto con el carácter ^, indicando que puede aparecer cualquier carácter menos los que están en el conjunto.

var myRegex = /[^scm]al/gi;
tal, sal, perro, mal, sota, cal, tal, salir, ramal.

Se puede especificar un rango mediante un guión: [0-7] indica números del 0 al 7, [a-z] indica letras de la 'a' a la 'z'

'^' y '$' Principio y final de línea

Muchas veces es necesario seleccionar una cadena sólamente si está al principio o al final de un texto. Por ejemplo, es muy común eliminar los espacios en blanco que puedan aparecer al principio o al final.

Sabemos que el símbolo \s selecciona un espacio en blanco. Con la siguiente expresión seleccionariamos todos los espacios al principio de un texto:

var myRegex = /^\s+/g;

Estamos buscando un 'principio de texto' seguido de uno ó mas espacios en blanco:

   Este es mi texto.

Si queremos seleccionar los del final:

var myRegex = /\s+$/g;

Estamos buscando uno ó mas espacios en blanco justo antes de un final de texto:

Este es mi texto.   

En el apartado de flags vimos que existe uno para modificar el comportamiento de estos dos carácteres. El flag 'm', cuando está presente indica que el texto es multilínea y queremos que ^ y $ seleccionen principio y final de cada línea, en vez de principio y final de texto.

\s, \d, \D, \w, \W .... Símbolos para hacer la vida más facil

Algunos conjuntos de caracteres son de uso tan común que se han creado unos símbolos para incluirlos más fácilmente. Por ejemplo, para indicar que un carácter debe ser un número podemos escribir [0-9] o podemos utilizar \d (digit) que es mucho más sencillo. Cuando el símbolo aparece en mayúscula significa exactamente lo contrario. \D sería cualquier carácter que NO sea un número.

Los más utilizados son:

\sCualquier tipo de espacio en blanco ( espacio, tabulador, salto de línea, etc)
\STodo carácter que NO sea un espacio en blanco
\tTabulador
\wCualquier carácter alfanumérico. Equivalente a [a-zA-Z0-9_]
\WCualquier carácter NO alfanumérico
\dUn dígito. Equivale a [0-9]
\DCualquier carácter que NO sea una dígito
\b'Word Boundary'. Marca el inicio o el fin de una palabra ( similar a '^' y '$' para una línea)

Como ejemplo podemos escribir una expresión para comprobar un nombre de usuario. Suponemos que el nombre de usuario no puede contener espacios y sólo puede contener números y letras (incluimos tambien el guión bajo o subrayado). Tiene que tener un mínimo de 6 carácteres y un máximo de 12:

var myRegex = /^\w{6,12}$/g;

Sólo seleccionará un nombre de usuario cuando cumpla las condiciones. En caso contrario no seleccionará nada. Puede utilizarse en una condición de JavaScript para aceptarlo o rechazarlo.

'|' OR

La barra vertical nos permite definir varias expresiones diferentes para buscar una o otra:

var myRegex = /perro|gato/gi;
perro, loro, gato, hamster, serpiente.

Se busca la coincidencia con la expresión de la de la derecha o de la de la izquierda completas. Si queremos que el OR sólo afecte a una parte de la expresión, tenemos que encerrar esa parte entre paréntesis. Por ejemplo, si queremos buscar 'elefante' y 'elemento'

var myRegex = /ele(mento|fante)/gi;
elegante, elemento, testamento, elefante, chanante, elemental.

Tambien podemos definir más de dos opciones:

var myRegex = /ele(mento|fante|gante)/gi;
elegante, elemento, testamento, elefante, chanante, elemental.

() Creando subexpresiones en nuestra regex

Los paréntesis se utilizan para marcar una parte de la expresión sobre la que actúa algún operador. En el ejemplo anterior vimos que podemos delimitar el rango sobre el que actuaba OR para que no se aplicara a la expresión completa. También podemos extender el rango sobre el que actuan los cuantificadores.

var myRegex = /1(23)+/gi;

En la expresión anterior el '+' se aplica al grupo completo '23'.

123, 1232323, 1232222, 123333333, 12323232323.

Sin los paréntesis los cuantificadores se aplican sólo al carácter inmediatamente anterior:

var myRegex = /123+/gi;
123, 1232323, 1232222, 123333333, 12323232323.

Los paréntesis tienen también otra función: memorizan todas las coincidencias que se encuentren en el texto y pueden utilizarse despues como $1, $2, $3, etc. Sólo se memoriza y guarda (en $n) la parte de la expresión marcada con los paréntesis. Por ejemplo, en una expresión que seleccione fechas, podemos encerrar entre parentesis la parte que coge el dia y el mes y luego cambirlo de orden colocando $2-$1.

Podemos utilizar ?: para evitar que se memorize el contenido:

var myRegex = /1(?:23)+/gi;

Esto es todo por ahora. En otro tutorial veremos las funciones que tenemos disponibles para utilizar las expresiones regulares de varias formas en JavaScript.

Fuentes: MDN Regular Expressions

Regular expression enlightenment
Regexpal (regex tester)

jueves, 30 de mayo de 2013

Simular "text-overflow:ellipsis" para textos largos ( multilínea )

En CSS, si utilizamos las siguientes propiedades:
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Podemos conseguir que un texto se corte a la anchura que queramos y se inserten automáticamente unos puntos suspensivos al final.



El problema es que sólo funciona para una única línea. El texto se corta cuando la línea sobrepasa la anchura del contenedor. Necesitamos incluir white-space: nowrap para evitar que el texto que no cabe se desplace a la línea siguiente.
Sería muy util poder hacer también algo como esto:



Es decir, tenemos un texto largo, de varias líneas ( sin white-space: nowrap ), y queremos que se corte mostrando "..." cuando supera la altura del contenedor.

Ellipsis multilínea con JavaScript

Sólo son necesarias unas líneas de JavaScript para conseguirlo. Supongamos que tenemos un texto largo en un div contenedor con una altura limitada:
<div id="container">
  <p>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  </p>
</div>

Tenemos el siguiente CSS para limitar la altura y anchura del contenedor y ocultar el texto que no cabe:
#container {
  width: 300px;
  height: 95px;
  overflow: hidden;
}
El texto quedaría cortado pero no tenemos ninguna indicación de ello:



Podemos solucionar el problema con unas lineas de JavaScript (y JQuery):
var containerHeight = $("#container").height();
var $text = $("#container p");

while ( $text.outerHeight() > containerHeight ) {
        $text.text(function (index, text) {
            return text.replace(/\W*\s(\S)*$/, '...');
       });
}
El resultado es:



Lo que hace este pequeño script es ir recortando palabras del final del texto hasta que la altura del texto es igual a la altura del contenedor.Toda la magia se realiza con la expresión regular:
text.replace(/\W*\s(\S)*$/, '...');
Básicamente esta instrucción sustituye la última palabra, junto con cualquier espacio en blanco que tenga delante, por "...". Esto se va repitiendo hasta que el texto encaja dentro del contenedor.

Creando una función genérica

Podemos hacerlo más flexible creando una función que incluya los "..." en cualquier texto largo que sobrepase el contenedor. Pasaremos como parámetro el id del div que contiene el texto.
La siguiente función es válida siempre que el marcado HTML sea como en el ejemplo anterior,el texto debe estar englobado en un <p></p> que está dentro del elemento que se pasa como parámetro. No es dificil cambiar la función para otra estructura.
function createEllipsis ( containerId ) {
    $container = $("#" + containerId);
    var containerHeight = $container.height();
    var $text = $container.find("p");

    while ( $text.outerHeight() > containerHeight ) {
        $text.text(function (index, text) {
            return text.replace(/\W*\s(\S)*$/, '...');
        });
    }
  }

Se utilizaría:
createEllipsis("container");

Hay tambien una solución para hacer algo parecido con CSS descrita en el post Multiline ellipsis in pure css pero tiene varios problemas. Necesita algún marcado extra en el HTML y, la más grave, si la línea en la que cortamos es corta (un punto y aparte), los puntos suspensivos quedan siempre pegados a la derecha y queda un hueco en blanco.

sábado, 25 de mayo de 2013

JavaScript: Cómo comprobar si un elemento existe en el DOM

Es muy común tener que comprobar si un elemento HTML existe en el documento que estamos manejando. Vamos a ver a continuación dos formas de hacerlo, una con JavaScript puro y la otra utilizando jQuery.

Vamos a suponer que el elemento en cuestión tiene una id que para nuestro ejemplo será elementID.

JavaScript puro

En JavaScript podemos utilizar document.getElementById:


if ( document.getElementById( "elementID" )) {
      //...
}

En realidad estamos intentando seleccionar el elemento con id="elementID". Si el elemento existe, document.getElementById() lo devuelve ( es un elemento del DOM y equivale a true ). Si no existe devuelve null que equivale a false.

Usando jQuery

En jQuery no nos basta con intentar simplemente seleccionar el elemento:


// esto no funciona!!
if ( $("#elementID") ) {
      //...
}

No funciona porque jQuery siempre devuelve un array de elementos, aunque sólo haya uno o incluso aunque no haya ninguno. Si el elemento no existe devolverá un array vacío, que equivale a true. Por lo tanto tenemos que comprobar el número de elementos devueltos:


if ( $("#elementID").length ) {
     //...
}

Cuando el array está vacio $("#elementID").length es 0 y por lo tanto false, para cualquier otro número será true.

miércoles, 22 de mayo de 2013

No abuses de las librerias externas

Las librerias externas como jQuery o underscore son una gran ayuda para desarrollar aplicaciones grandes. Son librerias de utilidades genericas y normalmente su uso es apropiado. El problema viene cuando algunos desarrolladores buscan una librería para cada elemento que tienen que desarrollar. He trabajado en algun proyecto en el que me he encontrado más de veinte plugins de jQuery como parte de una applicación.


Programación Frankenstein: crear una aplicación monstruosa a base de 'coser' librerías que hacen alguna cosita

Esto es la programación Frankenstein, crear una aplicación monstruosa a base de 'coser' librerías que hacen alguna cosita. Si mi aplicacion necesita un mosaico de fotos, busco un plugin que me lo haga. Si necesito un scroll personalizado busco un plugin, si necesito visualizar las fotos como un carrusel, busco un plugin, etc, etc.

No quiero decir que no utilicemos una librería o un plugin cuando se adapte perfectamente a nuestras necesidades o cuando proporcione una funcionalidad que tardaríamos meses en desarrollar, pero el abuso de códigos externos nos puede llevar a crear un monstruo inmantenible.

La mayoría de las veces lo que necesitamos es una versión muy reducida de lo que proporciona la librería, que tiene que ser flexible para soportar las necesidades de usuarios diferentes. Habrá cientos de líneas que no utilicemos y algunos errores sin solucionar que nos obligarán a modificar un código que no conocemos.

Los problemas principales son:

  • Tenemos en la aplicación miles de líneas de código que no conocemos
  • Tenemos, probablemente, miles de líneas de código que no usamos
  • Aparecerán errores que nos obligarán a rastrear y modificar estas librerias externas
  • El cliente, tarde o temprano, solicitará alguna funcionalidad/cambio que nos obligará a cambiar el código de las librerias
  • Una vez que cambiemos un código externo, la actualizacion de las librerias se convierte en una pesadilla. Tendremos que pasar todos los cambios a las nuevas versiones o bien estar atentos a los cambios que solucionan errores e incorporarlos en nuestra versión

Cedric Dugas publica en su blog una excelente entrada sobre este tema titulada Muerte a las librerías monolíticas en el que expone algunos problemas que ha encontrado con el abuso de librerias y propone hacerse las siguientes preguntas antes de incorporar código de terceros:

  • ¿Puedo programarlo yo fácilmente?
  • ¿Tengo tiempo para hacerlo?
  • ¿Que navegadores soporta esta librería?
  • ¿Cuantos errores sin resolver tiene ( open issues )?
  • ¿Que tamaño tiene?
  • ¿El autor es de confianza?

Me parece una buena check list para reflexionar un poco antes de 'crear un monstruo'.

Fuentes:

Death to monolithic libraries