sábado, 1 de febrero de 2014

Atributos booleanos en HTML

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

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

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

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

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


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

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

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

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

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

Atributos booleanos y javaScript

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

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


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


Si tenemos el siguiente código HTML:

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

Que nos mostraría lo siguiente:

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

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

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

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

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

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

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

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

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

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

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

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

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

Conclusión

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

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

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

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