JavaScript no obstructivo
De MDC
Tabla de contenidos |
[editar] Introducción
JavaScript es una herramienta maravillosa para mejorar la usabilidad de las aplicaciones Web. Es una capa extra entre la estructura (HTML) y el diseño(CSS). Javascript nos permite alterar el comportamiento de un elemento.
Uno de los problemas con los que nos encontramos a la hora de desarrollar aplicaciones Web con JavaScript, son los problemas de accesibilidad derivados al no contemplar la posibilidad de que un usuario nos visite con un navegador que no interprete este lenguaje. Una técnica para corregir este problema sería el separar JavaScript de las otras 2 capas del desarrollo Web (estructura y diseño), esto recibe el nombre de JavaScript no obstructivo o Javascript accesible.
La frase, ¡¡Divide y vencerás!! se adapta perfectamente a esta idea, en la cual separaremos cada capa en su respectivo fichero, de forma que en cuanto a mantenimiento (la etapa más costosa y larga del desarrollo de una aplicación) e incluso la comprensión de la aplicación, se verán afectadas positivamente en cualquier aplicación.
[editar] Operación limpieza
El desarrollo Web ha cambiado en los últimos años, estamos dejando de mezclar la presentación de la estructura, lo que nos está facilitando el trabajo de mantenimiento, modificación y mejora de nuestras aplicaciones. El código que hace años usábamos está dando lugar a otro más complejo y fácil de mantener.
HTML:
<table border="0" cellpadding="0" cellspacing="5" width="100%"> <tbody><tr> <td><font face="Arial" size="-2">Lorem Ipsum</font></td> </tr> </tbody></table>
Este código con todos los atributos referentes a la presentación en el código, nos obliga a recorrer toda la aplicación para modificar cualquier aspecto que deseemos modificar. Por este motivo, nuestro código empieza a coger un poco de sensatez y nos permite separar las 2 capas, diseño y estructura.
CSS:
td.content {font-family:Arial,sans-serif;font-size:12px;}
HTML:
<table
style="" cellpadding="0" cellspacing="5">
<tbody><tr>
<td class="content">Lorem Ipsum</td>
</tr>
</tbody></table>
Y aún podemos ir más allá…
CSS:
body {font-family:Arial,sans-serif;font-size:12px;}
p {padding:5px;}
HTML:
<p>Lorem Ipsum</p>
La misma evolución ha sufrido el desarrollo con JavaScript.
[editar] Reglas del JavaScript no obstructivo
1. Nunca, bajo ninguna circunstancia incluyas JavaScript directamente en el documento.
Una de las mayores ventajas del JavaScript es que al igual que CSS permite la ejecución en ficheros externos. Esto nos permite incluir los ficheros que necesitemos para cada página, ajustando al máximo el peso de la aplicación. Además si has de cambiar alguna funcionalidad de la aplicación, únicamente has de modificar el fichero JS en cuestión. Pero para poder disfrutar de esta propiedad siempre tendremos que hacer referencia a nuestro/s JavaScript desde el fichero HTML, añadiendo entre los tags la siguiente línea y haciendo alusión al fichero que deseamos adherir a nuestro proyecto.
<script type="text/javascript" src="scripts.js"></script>
2. JavaScript es una mejora, no un sistema de seguridad.
Nosotros sólo podemos usar JavaScript como una mejora, debemos pensar que no siempre podremos disponer de él y que agentes externos pueden deshabilitarlo sin darnos opción a activarlo, así que hemos de programar pensando en que cualquier persona en este problema también debe poder usar nuestra aplicación.
Para ello podemos ver un ejemplo de JavaScript en el que vemos como valida un formulario.
HTML:
<form action=”index.php” onsubmit=”return checkform(this)”</b>> <p><label for=”login”>Login:</label> <input type=”text” name=”login” id=”login” /></p> <p><label for=”pw”>Password:</label> <input type=”password” name=”pw” id=”pw” /></p> <p><input type=”submit” value=”send” /></p> </form>
JavaScript:
function checkform(f)
{
var error='’;
error+=f.login.value=='’?'nlogin’:'’;
error+=f.pw.value=='’?'npassword’:'’;
if (error!='’)
{
alert(’Por favor ingrese lo siguiente:’+error);
}
return error=='’;
Esto es perfectamente válido, nos permite controlar el formulario sin perjudicar a los usuarios que no tienen JavaScript. De modo que en caso de no tenerlo activado, únicamente tendremos que tener en cuenta que en la página de recepción de datos tendremos que validarlos directamente en el servidor, devolviendo al usuario a la página anterior en caso de que no sean válidos.
Otra forma de afrontar el problema sería la siguiente.
HTML:
<form action=”index.php”> <p><label for=”login”>Login:</label> <input type=”text” name=”login” id=”login” /></p> <p><label for=”pw”>Password:</label> <input type=”password” name=”pw” id=”pw” /></p> <p><input type=”button” onclick=”checkform()” value=”send” /></p> </form>
JavaScript:
function checkform()
{
var f=document.forms[0];
var error='’;
error+=f.login.value=='’?'nlogin’:'’;
error+=f.pw.value=='’?'npassword’:'’;
if (error!='’)
{
alert(’Por favor ingrese lo siguiente:’+error);
} else {
f.submit();
}
De esta forma estamos condenando al fracaso nuestra aplicación, ya que si el navegador de nuestro usuario no soporta la llamada onclick, no podrá realizar un submit ya que nuestro botón es del tipo button.
3. Comprueba la disponibilidad de un objeto antes de acceder a él.
Muchos de los errores en JavaScript se deben a que se intenta acceder a elementos o métodos que no existen en un determinado momento del periodo de ejecución. Para solucionar esto basta con realizar un pequeña comprobación antes de utilizar dicho elemento o método.
JavaScript:
function color(o,col)
{
if(o)
{
o.style.background=col;
}
}
Antes de usar el elemento o, comprobamos que exista, así
conseguimos una robustez en nuestra aplicación que el usuario
agradecerá. Esta técnica es [http://www.anieto2k.com/2006/10/10/detectar-ie7-mediante-javascript/ muy usada para el famoso cross-browsing.
4. Crea un JavaScript no específico para un navegador
Quizas la regla más complicada de todas, pero la más importante. Al igual que tenemos que tener en cuenta navegadores sin JavaScript hemos de tener en cuenta todos los navegadores, para ello usaremos la regla anterior como la mejor baza a nuestro favor.
JavaScript:
function doDOMstuff()
{
if(document.getElementById && document.createTextNode)
{
[…]
}
}
Comprobamos la existencia del método getElementById y createTextNode del objeto document antes de usarlo, así nos aseguramos una victoria al lanzar esta función.
5. No uses variables de otros scripts.
Cuando creemos una función o funcionalidad debemos estar seguros de usar variables locales para dicha función o funcionalidad. De esta forma nos prevenimos de que otras funciones o funcionalidades modifiquen nuestras variables.
JavaScript:
var i=42; // variable global
function dothings()
for (i=0;i<200;i++) // i ha cambiado
{
// algún código
}
function dothingsright()
{
var i; // define i localmente
for (i=0;i<200;i++)
{
// algún código
}
}
alert(i); // = 42
dothingsright()
alert(i) // = 42 (debido a la definición local)
dothings()
alert(i) // = 200, oops!
6. Mantén los efectos de ratón de forma independiente
Tener en cuenta que el JavaScript esté activo o no no es
suficiente. Hemos de tener en cuenta que hay usuarios que no pueden
utilizar el ratón para navegar como normalmente se hace, esto obliga a
usar el teclado para realizar todas las tareas que requiera nuestra
aplicación. Un problema típico es el onchange
o onblur en los elementos de un formulario ya
que el teclado ejecuta estos eventos de forma distinta que el ratón.
HTML:
<form> <p> <label for=”go2″>Go to:</label> <select name=”go2″ id=”go2″ onchange=”send(this)”> <option value=”index.html”>Home</option> <option value=”chapter1.html”>Operation Cleanout</option> <option value=”chapter2.html”>Reaching things</option> </select> </p> </form>
JavaScript:
function send(f) var chosen; chosen=f.options[f.selectedIndex].value; self.location=chosen; }
El problema que tenemos aquí es que cada vez que lo hacemos nos
desplazamos hacia abajo desde nuestro teclado, estamos realizando la
función send(), no siendo el resultado
deseado (en FF parece no pasar). Los usuarios con experiencia sabrán
que usan alt+abajo y podrán omitir este evento.
La forma de solucionar esto, es delegando la obtención del
valor al evento onsubmit, de forma que
podremos realizar cuantos cambios deseemos sobre nuestro formulario y
únicamente lanzará la función al realizar el submit.
HTML:
<form action=”send.php” method=”post” onsubmit=”return send(this)”> <p> <label for=”go2″>Go to:</label> <select name=”go2″ id=”go2″> <option value=”index.html”>Home</option> <option value=”chapter1.html”>Operation Cleanout</option> <option value=”chapter2.html”>Reaching things</option> </select> <input type=”submit” value=”go” /> </p> </form>
JavaScript:
function send(f)
{
var chosen;
chosen=f.go2.options[f.go2.selectedIndex].value;
self.location=chosen;
return false;
}
PHP:
<?PHP if(isset($_POST[’go2′])){
header(’Location:’.$_POST[’go2′]);
}?>
La opción de PHP sería para los casos en los que el navegador no permite la ejecución de código JavaScript.
¿Qué pasa con onkeypress?
Según la W3C, en estos casos.
Otherwise, if you must use device-dependent attributes, provide redundant input mechanisms (i.e., specify two handlers for the same element):
- Use “onmousedown” with “onkeydown”.
- Use “onmouseup” with “onkeyup”
- Use “onclick” with “onkeypress”
Esto suena perfecto en la teoría pero en la vida real el
evento onkeypress está mal soportado por
diferentes navegadores. Los usuarios que dependen del teclado para
navegar normalmente tienen una configuración para simular los clicks,
intro, o espacio. Entonces esto nos lanza el evento onclick.
[editar] Cómo alcanzar lo que deseamos cambiar
Para un desarrollador javascript inexperto, el HTML es un patio de juegos.
HTML:
<a href=”index.html” onmouseover=”image1.src=’1on.gif’” onmouseout=”image1.src=’1off.gif’”> <img src=”1off.gif” name=”image1″ border=”0″ height=”150″ width=”150″ alt=”home”></a>
O incluso puede ser un poco más avanzado…
HTML:
<a href=”index.html” onmouseover=”roll(’home’,1)” onmouseout=”roll(’home’,0)”> <img src=”home.gif” name=”home” border=”0″ height=”150″ width=”150″ alt=”home”></a>
Javascript:
// preloading image
homeoff = new Image();
homeoff.src = ‘home.gif’;
homeon = new Image();
homeon.src = ‘homeoff.gif’;
function roll(imgName,a)
{
imgState=a==0?eval(imgName + ‘on.src’):eval(imgName + ‘off.src’);
document.images[imgName].src = imgState;
}
En cualquier caso los eventos son llamados desde el HTML y si la función cambia de nombre tendremos que cambiarlo en cada documento. Para ello debemos saber qué usar y cuándo usarlo, por ejemplo para este rollover, usaremos CSS que cumple nuestra misión sin alterar en nada la accesibilidad, ni el resultado.
HTML:
<a href=”index.html”><img src=”home.gif” id=”home” alt=”home”></a>
[editar] Subir por las ramas del árbol del nodo
Cada fichero XML (en ellos se incluye el xHTML) es como un árbol. Un nodo es una parte de este árbol y al igual que en un árbol para subir a un rama en la copa del árbol, tendremos que pasar por todas las ramas que nos encontremos por el camino, no podemos saltarnos ninguna. En javascript disponemos de una buena serie de herramientas que nos permiten recorrer este árbol y poder manipular las propiedades de un elemento de dicho árbol.
Funciones para alcanzar un elemento en la página
getElementById(’elementID’) Devuelve el elemento con el ID asignado
getElementsByTagName(’tag’) Devuelve todos los elementos con la etiqueta name (tag)
Funciones para recorrer las cercanias de un elemento
childNodes Devuelve un array con todos los hijos que cuelguen del elemento. También disponemos de firstChild y lastChild, que son versiones reducidas de childNodes[0] y childNodes[this.childNodes.length-1].
parentNode Nos devuele el elemento padre
nextSibling El siguiente elemento al mismo nivel dentro del árbol.
previousSibling El elemento anterior al mismo nivel dentro del árbol.
Atributos y funciones para elementos
attributes Devuelve un array con todos los atributos del elemento.¿Cómo no? No funciona con Internet Explorer 6 o inferior.
data Devuelve o asigna los datos textuales del nodo
nodeName Devuelve el nombre del nodo (El nombre de elemento HTML)
nodeType Devuelve el tipo de nodo
- Es un elemento nodo
- Un atriburo
- Un texto
nodeValue Devuelve o asigna el value de un nodo.
getAttribute(attribute) Devuelve el valor del atributo demandado.
Conociendo estas funciones y atributos, podremos mejorar nuestro código y hacer algo parecido a esto.
HTML:
<a href=”index.html”><img src=”home.gif” class=”roll” alt=”home”></a>
Javascript:
function findimg()
{
var imgs,i;
// loop through all images of the document
imgs=document.getElementsByTagName(’img’);
for(i=0;i<imgs.length;i++)
{
// test if the class ‘roll’ exists
if(/roll/.test(imgs[i].className))
{
// add the function roll to the image onmouseover and onmouseout and send
// the image itself as an object
imgs[i].onmouseover=function(){roll(this);};
imgs[i].onmouseout=function(){roll(this);};
}
}
}
function roll(o)
{
var src,ftype,newsrc;
// get the src of the image, and find out the file extension
src = o.src;
ftype = src.substring(src.lastIndexOf(’.'), src.length);
// check if the src already has an _on and delete it, if that is the case
if(/_on/.test(src))
{
newsrc = src.replace(’_on’,'’);
}else{
// else, add the _on to the src
newsrc = src.replace(ftype, ‘_on’+ftype);
}
o.src=newsrc;
}
window.onload=function(){
findimg();
}
Bien por el momento, aunque no hemos olvidado una cosa, ahora esta imagen realizará un roll-over al pasar el ratón por encima, pero tenemos que pensar en los usuarios sin ratón. Para conseguir esto debemos checkear si el enlace de alrededor tiene el focus o no. Para ello usaremos el comando parentNode de nuestro objeto de la siguiente forma.
Javascript:
function findimg()
{
var imgs,i;
// Loop through all images, check if they contain the class roll
imgs=document.getElementsByTagName(’img’);
for(i=0;i<imgs.length;i++)
{
if(/roll/.test(imgs[i].className))
{
// add the function roll to the parent Element of the image
imgs[i].parentNode.onmouseover=function(){roll(this);};
imgs[i].parentNode.onmouseout=function(){roll(this);};
imgs[i].parentNode.onfocus=function(){roll(this);};
imgs[i].parentNode.onblur=function(){roll(this);};
}
}
}
function roll(o)
{
var i,isnode,src,ftype,newsrc,nownode;
// loop through all childNodes
for (i=0;i<o.childNodes.length;i++)
{
nownode=o.childNodes[i];
// if the node is an element and an IMG set the variable and exit the loop
if(nownode.nodeType==1 && /img/i.test(nownode.nodeName))
{
isnode=i;
break;
}
}
// check src and do the rollover
src = o.childNodes[isnode].src;
ftype = src.substring(src.lastIndexOf(’.'), src.length);
if(/_on/.test(src))
{
newsrc = src.replace(’_on’,'’);
}else{
newsrc = src.replace(ftype, ‘_on’+ftype);
}
o.childNodes[isnode].src=newsrc;
}
window.onload=function(){
findimg();
}
[editar] Creando y destruyendo contenido
Una de las fortalezas de DOM es que no es únicamente de lectura, y esto nos permite modificar más aun nuestra aplicación. Para ello disponemos de una serie de funciones y métodos que nos facilitarán el trabajo.
Crear un nodo
createElement(element) Crea un nuevo elemento
createTextNode(string) Crea un elemento de texto con el valor string
Alterando un contenido existente
setAttribute(attribute,value) Añade un nuevo valor al atributo del objeto
appendChild(child) Añade un nodo hijo a la lista de childNode del objeto, el hijo debe de ser un objeto, no un texto.
cloneNode() Copia el nodo completo con todos sus hijos.
hasChildNodes() Comprueba que el nodo no sea un nodo hoja, osea que tenga hijos.
insertBefore(newchild,oldchild) Crea un nuevo hijo (newchild) antes del hijo antiguo (oldchild).
removeChild(oldchild) Borra un hijo.
replaceChild(newchild,oldchild) Reemplaza el viejo hijo (oldchild) por el nuevo (newchild).
removeAttribute(attribute) Elimina un atributo del objeto.
Vamos a ver un ejemplo práctico y accesible. Imaginar que queremos hacer una lista en la cual tendremos enlaces a imagenes, si no disponemos de Javascript, abriremos una ventana nueva para ver la imagen seleccionada.
HTML:
<ul id=”imglist”> <li><a href=”home.gif” target=”_blank”>Home (new window)</a></li> <li><a href=”home_on.gif” target=”_blank”>Home active (new window)</a></li> <li><a href=”jscsshtml.gif” target=”_blank”>HTML-CSS-Javascript (new window)</a></li> </ul>
De este modo, tenemos una forma válida de mostrar una lista de
imagenes, al hacer click sobre el enlace el target
actuará y mostrará la imagen en una ventana nueva. Pero, ¿si disponemos
de javascript? ¿por qué no podemos enriquecer el aspecto?
Si disponemos de Javascript y DOM vamos a hacer lo siguiente:
- Eliminar el texto (new window) de los links
- Añadir al evento
onclickla llamada a la funciónpopw()
Javascript:
function imgpop()
{
var il,imga,imgatxt;
// get all LIs in imagelist, loop over them
il=document.getElementById(’imglist’).getElementsByTagName(’li’);
for(i=0;i<il.length;i++)
{
// grab first link in the LI
imga=il[i].getElementsByTagName(’a')[0];
// delete the wording (new window) in the link text
// (which is the nodeValue of the first node)
imgatxt=imga.firstChild;
imgatxt.nodeValue=imgatxt.nodeValue.replace(/ (new window)/,'’);
// add the event handlers to call popw();
imga.onclick=function(){return popw(this);}
//imga.onkeypress=function(){return popw(this);}
}
}
La función debe:
- Mostrar la imagen debajo del link.
- Ocultar la imagen en caso de ya estar visible.
- Hacer que la imagen desaparezca al hacer click sobre ella.
Javascript:
function popw(o)
{
var newimg;
// if there is already an image in the parentNode (li)
if(o.parentNode.getElementsByTagName(’img’).length>0)
{
// delete it
o.parentNode.removeChild(o.parentNode.getElementsByTagName(’img’)[0]);
} else {
// else, create a new image and add a handler that removes it when you
// click it
newimg=document.createElement(’img’);
newimg.style.display=’block’;
newimg.onclick=function(){this.parentNode.removeChild(this);};
newimg.src=o.href;
o.parentNode.appendChild(newimg)
}
return false;
}
Podéis ver el ejemplo de mano del autor.Activar y desactivar el javascript para ver las diferencias.
[editar] ¿Cómo llamar a las funciones?
La parte más importante de la separación del Javascript de nuestro HTML, es la forma en la que llamamos a las funciones, para ello hasta ahora usabamos los métodos que incorporan los tags HTML.
HTML:
<body onload=”foo();”>
Esto debe cambiar, y aprovechándonos de que sabemos cómo obtener un objeto concreto a partir del DOM de nuestro HTML, podemos asignar estos eventos desde javascript sin tener que influir en nuestro HTML.
Javascript:
window.onload=foo;
or
window.onload=function(){
foo();
bar();
baz();
}
Pero aun podemos mejorarlo más todavía, y para ello usaremos los gestores de eventos.
Javascript:
function addEvent(obj, evType, fn)
if (obj.addEventListener){
obj.addEventListener(evType, fn, false);
return true;
} else if (obj.attachEvent){
var r = obj.attachEvent(”on”+evType, fn);
return r;
} else {
return false;
}
}
addEvent(window, ‘load’, foo);
addEvent(window, ‘load’, bar);
Esto nos permite desligar por completo nuestra capa de estrucutura de la funcional de forma eficiente y estandarizada. Obviamente con IE en Mac tendremos problemas con esta función.
[editar] Separacion de CSS y Javascript
Quizás en los ejemplos anteriores no se está usando la forma más apropiada, pero es bastante clara para demostrar lo que queremos expresar. La idea desde un principio es separar de forma bien definida las 3 capas que influjen en una aplicación web. Por eso debemos evitar código parecido a estos.
Javascript:
if(!document.getElementById(’errormsg’)){
var em=document.createElement(’p');
em.id=’errormsg’;
em.style.border=’2px solid #c00′;
em.style.padding=’5px’;
em.style.width=’20em’;
em.appendChild(document.createTextNode(’Please enter or change the fields marked with a ‘))
i=document.createElement(’img’);
i.src=’img/alert.gif’;
i.alt=’Error’;
i.title=’This field has an error!’;
em.appendChild(i);
}
Esto además de no ser nada claro, es un tedio a la hora de ponerte a modificar cualquier opción o añadir cualquier mejora. Por eso hemos de delegar este tema del aspecto a los CSS. Para ello usaremos el nexo entre la capa del diseño y la capa funcional, los "tags".
Todos los tags tienen 2 atributos como mínimo, ID
y class. ID se
refiere al nombre único que le damos a un elemento concreto de la
imagen, los ID’s nunca deben repetirse en la página. Después tenemos class,
que asignar un aspecto a nuestro elemento, en cierta manera el class
engloba a todos los elementos designados dentro de dicha clase.
Sintaxis de multiples clases
Los elementos pueden tener más de una clase, simplemente hay que separarlos por espacios. Esto está soportado por los navegadores más modernos. IE en Mac no le gusta mucho cuando una clase conteniene el nombre de otra.
Aplicando clases via Javascript
Para cambiar la clase de un elemento debemos atacar al
atributo className del objeto.
HTML:
<ul id=”nav”> […] </ul>
Javascript:
if(document.getElementById && document.createTextNode)
{
if(document.getElementById(’nav’))
{
document.getElementById(’nav’).className=’isDOM’;
}
}
Esto no origina que nuestro CSS pueda tener dos estados, uno
el que ataque al ID directamente #nav
y otro que ataque al ID más la clase #nav.isDOM
CSS:
ul#nav{
[...]
}
ul#nav.isDOM{
[...]
}
Usando el operador += podremos
añadir una o más clases a nuestro elemento.
Javascipt:
if(document.getElementById && document.createTextNode)
{
var n=document.getElementById('nav');
if(n)
{
n.className+=n.className?’ isDOM’:'isDOM’;
}
}
[editar] Información original del documento
- Author(s): Christian Heilmann
- Link: http://onlinetools.org/articles/unobtrusivejavascript/
- Traducción: http://www.anieto2k.com/