Esto pretende ser una traducción (de las mias) de un fabuloso artículo de Christian Heilmann.
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 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 el 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 perféctamente 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.
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 usabamos esta dando lugar a otro más complejo y fácil de mantener.
HTML:
<table border="0" cellpadding="0" width="100%" cellspacing="5">
<tr>
<td><font face="Arial" size="-2">Lorem Ipsum</font></td>
</tr>
</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 cellpadding="0"
style="width:100%;border:none" cellspacing="5">
<tr>
<td class="content">Lorem Ipsum</td>
</tr>
</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.
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áginas, 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 <head></head> la siguiente línea 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 solo podemos usar Javascript como una mejora, debemos de pensar que no siempre podremos disponer de él y que agentes externos pueden deshabilitarlo sin darnos opción a activarlo, asi que hemos de programar pensando en que cualquier persona en este problema tambien 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)">
<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('Please enter the following:'+error);
}
return error=='';
}
Esto es perfectamente válido, nos permite controlar el formulario sin perjudicar a los usuarios que no tiene 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('Please enter the following:'+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 boton es del tipo button.
3. Checkea 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 muy usada para el famoso cross-browsing.
4. Crea un Javascript no especifico 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; // global variable
function dothings()
{
for (i=0;i<200;i++) // i gets changed
{
// some code
}
}
function dothingsright()
{
var i; // define i locally
for (i=0;i<200;i++)
{
// some code
}
}
alert(i); // = 42
dothingsright()
alert(i) // = 42 (due to local definition)
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 aqui es que cada vez que 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 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.
¿Que 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
.
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 que usar y cuando 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>
Subir por los ramas del árbol del nodo
Cada fichero XML (en ellos se incluye el xHTML) es como un árbol. Un nodo una parte de este árbol, y al igual que en un arbol para subir a un rama en la copa del arbol 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 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 elemento con la etiqueta (tag)
Funciones para recorrer las cercanias de un elemento
- childNodes
- Devuelve un array con todos los hijos que cuelguen del elemento. Tambien 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.¿Como 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 de 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();
}
Creando y destruyendo contenido
Una de las fortalezas de DOM es que no es únicamente de lectura, y esto nos permite modificar más aún 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 se 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 esta forma tenemos una 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 que 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
onclick
la 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;
}
Podeis ver el ejemplo de mano del autor.Activar y desactivar el javascript para ver las diferencias.
¿Como 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 aprovechandonos de que sabemos como 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 aún podemos mejorarlo más aún, 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.
Separacion de CSS y Javascript
Quizas 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 parecidos 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 coñazo a la hora de ponerte a modificar cualquier opción o añadir cualquier mejora. Por eso hemos de delegar es 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. Despues tenemos class
, que asigna 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 separarlas por espacios. Esto esta 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
ul#nav{
[...]
}
ul#nav.isDOM{
[...]
}
Usando el operador +=
podremos añadir una o más clases a nuestro elemento.
if(document.getElementById && document.createTextNode)
{
var n=document.getElementById('nav');
if(n)
{
n.className+=n.className?' isDOM':'isDOM';
}
}
28 comentarios, 30 referencias
+
#