Ir directamente al contenido de esta página

codexexempla.org

Cómo hacer accesible un vínculo a ventana nueva

Tabla de contenidos

  1. La cuestión inicial
  2. El script
  3. Pruebas en navegadores

Aunque arriba en la fecha dice «Junio de 2007», en realidad este script es algo antiguo —en términos relativos en tiempo Web, unos ocho o nueve meses—. Eso significa que, aunque funciona correctamente, la experiencia adquirida desde entonces me ha hecho ver que había una solución más simple, y por tanto más elegante y efectiva. Si está visitando esta página porque necesita usar un script de estas características en su propia página, lea mejor este otro artículo. Pero si su objetivo es aprender, creo que es interesante que dedique algo de su tiempo a leer éste primero, porque es un ejemplo ideal de cómo a veces los programadores somos capaces de complicarnos la vida nosotros solos.

La cuestión inicial

Es de todos conocido —o debería ser— que en XHTML el atributo target de a se considera depreciado. En otras palabras, desaparece la opción de invocar el comportamiento del navegador que muestra contenidos en una ventana nueva. Ésta es una de las medidas que más mensajes genera en foros y listas de correo sobre accesibilidad. Personalmente, no soy partidario de llenar la pantalla del usuario de ventanas no deseadas, pero sí es cierto que hay casos en los que es lícito abrir una ventana nueva1. ¿Qué se puede hacer en estos casos?

Para encontrar una solución, primero hay que analizar cuáles son los principales problemas que suponen las ventanas emergentes:

En realidad, atendiendo a la pauta 10.1 de las WCAG 1.0, el primer problema se soluciona con algo tan sencillo como incluir tras cada vínculo a ventana nueva un aviso de ese comportamiento. Para no distraer la atención de un lector vidente, en lugar de incluir un vínculo subrayado con «el enlace anterior se abre en ventana nueva», empleo este texto como alt de un pequeño icono que coloco, efectivamente, tras cada vínculo a ventana nueva. Además, y por si la imagen es poco explicativa, añado para los usuarios videntes un title="Enlace a ventana nueva".

Bien, el problema de la confusión de los usuarios invidentes —en la medida de lo posible— está solucionado, ¿pero y el JavaScript deshabilitado? Bueno, es claro que los vínculos a ventana nueva deben mantener su funcionamiento por defecto, pero que de alguna manera tienen que ser identificables como vínculos «especiales» sobre los que aplicar escuchas desde el script.

A bote pronto, pienso en que si se tratase de popups con contenidos propios, no habría mucho problema: podría comenzar el nombre de todas las páginas destinadas a ventana emergente con «popup-», y buscar esta cadena literal en el href de cualquier vínculo pulsado. Pero esta solución no sirve para dar cuenta de contenidos a otras páginas web.

Sin embargo, me doy cuenta de que la solución está justo ante mis ojos: ¿acaso no están ya esos vínculos «especiales» indicados por la imagen que he añadido después de cada uno de ellos? Exacto. Como se ve, la aplicación de medidas de accesibilidad en un código estándar facilita la implementación de mejoras en la capa de comportamientos.

El script

Dicho y hecho, tras unas cuantas horas más, el resultado fue éste:


 01  var popups = new Array();
 02  
 03  function buscar_popups() {
 04      if (document.addEventListener) {
 05          var imagenes = document.getElementsByTagName('img');
 06          for (var j=0;j<imagenes.length;j++){
 07              if (imagenes[j].getAttribute('alt')=='[el enlace anterior se abre en ventana nueva]') {
 08              popups[popups.length] = imagenes[j].previousSibling;
 09              } 
 10          }
 11      } else {
 12          var vinculos = document.getElementsByTagName('a');
 13          for (var n=0;n<vinculos.length;n++){
 14              if(vinculos[n].nextSibling){
 15                  if (vinculos[n].nextSibling.alt=='[el enlace anterior se abre en ventana nueva]') {
 16                  popups.push(vinculos[n]);
 17                  }
 18              }
 19          }
 20      }
 21      escuchar_popups();
 22  }
 23  
 24  function escuchar_popups() {
 25      for (var k=0;k<popups.length;k++) {
 26          if (document.addEventListener) {
 27              popups[k].addEventListener('click', abrir_popup, false);
 28          } else {
 29              popups[k].onclick = function() { abrir_popup(this.href); return false; }
 30          }
 31      }
 32  }
 33  
 34  function abrir_popup(evento) {
 35      if (document.addEventListener) {
 36          var ventana_nueva = window.open(this, 'nueva_ventana','width=500px,height=300px,menubar=yes,resizable=yes,location=yes,scrollbars=yes,status=yes,toolbar=yes');
 37          ventana_nueva.focus();
 38          evento.preventDefault();
 39      } else {
 40          var ventana_nueva = window.open(evento, 'nueva_ventana','width=500px,height=300px,menubar=yes,resizable=yes,location=yes,scrollbars=yes,status=yes,toolbar=yes');
 41          ventana_nueva.focus();
 42          return false;
 43      }
 44  }
 45  
 46  if(document.addEventListener){ window.addEventListener('load',buscar_popups,false); } else { window.attachEvent('onload',buscar_popups); }
            

Veamos los puntos más conflictivos.

Primero, tras la detección de addEventListener, el primer paso era crear una matriz con todos los vínculos que tuviesen el icono de ventana nueva. Podía partir de una matriz previa con todos los vínculos, o bien de una con todas las imágenes. Sin embargo, por uno de esos misterios que envuelven JavaScript y su implementación en los navegadores, si la creaba para las imágenes, fallaba en Explorer; por el contrario, si la creaba para los vínculos, fallaba en los navegadores basados en Gecko. No me quedó más opción que crear ambos (líneas 5 y 12).

Sobre su respectiva matriz, para navegadores que soportan addEventListener —que es un buen indicador del soporte de DOM del navegador— creé el primer for de la función. Este bucle detecta las coincidencias del atributo alt de la imagen recogida en cada índice de la matriz, y lo compara con '[el enlace anterior se abre en ventana nueva]', que es el que me permite diferenciar el icono en cuestión del resto de imágenes que pueda incluir la página. Si el alt de la imagen coincide, sé que inmediatamente antes viene un vínculo a ventana nueva. Así, recojo ese vínculo —por medio de previousSibling— y lo almaceno en una nueva matriz, a la que llamo popups (líneas 6 a 10).

Para Explorer, el proceso es el mismo, sólo que como tengo contenido en la matriz la lista de vínculos, comparo el atributo alt del elemento posterior adyacente (línea 15) con el texto alternativo. De la misma manera, las coincidencias las almaceno en la misma matriz popups.

Pero no todo iba a ser tan sencillo. El método para añadir valores a una matriz es push(). Sin embargo, al añadir valores a la matriz que tenía por medio de popups.push(imagenes[j].previousSibling) —para navegadores que soportan addEventListener, descubrí un nuevo problema: Opera 9.

A pesar de que este navegador cuenta con un soporte serio de DOM y demás estándares, padece un bug: push() no añade valores a una matriz, sino que va sustituyendo el último valor de la misma por los nuevos. En consecuencia, para Opera 9, la longitud de popups siempre era 1, e incluía exclusivamente el último vínculo seleccionado de la página; éste era el único para el que el comportamiento esperado funcionaba.

Tras los primeros minutos de estupor y frustración, pensé que tendría entonces que añadir el valor asignándolo directamente al último índice de la matriz. ¿Cómo hacerlo? Bueno, aprovechando que los índices de una matriz comienzan por 0 y no por 1, me di cuenta de que si obtenía la longitud de la matriz, no tenía el índice del último elemento almacenado, sino el primero vacío tras el último elemento, que era justo el que necesitaba. Sin más preámbulos, sustituí el enunciado inicial de push() por popups[popups.length] (línea 8). Y funcionó.

Bien, tras esta pequeña crisis, contaba, para cada navegador, con la lista de vínculos a ventana nueva. Ya sólo faltaba añadir las escuchas de rigor y lanzar la función que abriría las ventanas.

Para los navegadores que lo soportan, en escuchar_popups() empleé el addEventListener de rigor para lanzar la función abrir_popup. Al invocar desde addEventListener una función, se envía implícitamente como parámetro una referencia al elemento que ha desencadenado el evento, y se puede hacer referencia a él por medio de this, que en este caso contiene el vínculo activado. Por último, no me quedaba más que abrir la ventana nueva, con el tradicional var ventana_nueva =  window.open(this, 'nueva_ventana'….

Tras esto, quedaban los detalles: otorgar el foco a la nueva ventana, por si ésta ya había sido abierta desde un vínculo anterior —ventana_nueva.focus()—, y prevenir el comportamiento por defecto —evento.preventDefault()—. Bueno, quedaban los detalles… y Explorer.

Replicando los enunciados de los navegadores basados en estándares en la parte de Explorer —con el enunciado de popups[k].attachEvent('onclick', abrir_popup), por supuesto—, no había manera, porque en este navegador al emplear this en abrir_popup, la función me devolvía un objeto genérico: ni this, ni this.href, me devolvían el vínculo2. Tras replantearme si debería haber escogido otra profesión, tuve que recurrir de nuevo a los métodos de vieja escuela.

En lugar de emplear attachEvent, en la rama de Explorer de escuchar_popups() incluí popups[k].onclick = function() { abrir_popup(this.href); return false; }, cuyo comportamiento a grandes rasgos es equivalente, y envié a la función de apertura this.href, porque a través de onclick a this sí se le asignaba la referencia al vínculo activado. A la vez, devolvía ya el false para evitar que el navegador siguiera el vínculo, ya que Explorer 6 no soporta preventDefault. Por fin, tras todo esto, el vínculo llegaba a abrir_popup. Y después de ello, de abrir la ventana nueva y otorgarle el foco, decidí incluir un nuevo return false, aún siendo consciente de que era redundante, y sospechando que no sería necesario. ¿Por qué? Por prudencia, y porque sabía que un nuevo problema a la hora de hacer las pruebas podría acarrearme un serio problema nervioso…

Para comprobar el funcionamiento del script, haga clic sobre este vínculo [el enlace anterior se abre en ventana nueva]; después desactive el soporte de JavaScript de su navegador y repita la operación.

Voilà.

Como ya he explicado arriba, un script más simple está disponible en «Cómo hacer accesible un vínculo a ventana nueva (2)»; por ello no incluyo aquí un vínculo de descarga del archivo .js de este artículo.

He comprobado que este script funciona correctamente —sobre Windows— en:

Notas

  1. Por ejemplo, cuando se vincula a un PDF. En este caso concreto, la apariencia del navegador varía al incluirse las barras del plugin, por lo que lo asociamos no a una página web, sino a un documento en una aplicación de escritorio. Al terminar de consultar el PDF, hacemos lo que solemos hacer en otras aplicaciones: cerrar la aplicación, no el documento. Por una cuestión de usabilidad, es bueno prevenir este comportamiento vinculando el PDF a una ventana nueva, para que al cerrarlo el usuario no cierre el navegador. Volver
  2. Cuando escribí estas notas, tiempo ha, no sabía el motivo de este comportamiento; ahora sí lo sé, y lo explico en «Cómo hacer accesible un vínculo a ventana nueva (2)». Volver

Contacto

En virtud de la Ley Orgánica 15/1999 de Protección de Datos de Carácter Personal le informo de que los datos que proporcione no serán empleados para otro fin que el de responder a su mensaje. En especial, me comprometo a no cederlos a terceros ni a emplearlos para enviar información no solicitada.

Del blog de Digital Icon