jueves, 19 de julio de 2007

Formulario con varios botones. Implementación usable y accesible

Artículos relacionados
[24-07-07] Enlaces que actúan como botones. Implementación accesible.
[28-02-08] Formularios usables: 60 Directrices de Usabilidad
[28-03-12] Detectar si una imagen se ha cargado. Descripción extensa de una imagen: accesible con lector de pantalla y visible sin imágenes activas

Imagina que tenemos que realizar un formulario muy sencillo:

Ejemplo visual del formulario que a continuación se explica

El formulario consta de un campo NIF de tipo texto y tres botones que realizan tres operativas diferentes: "Crear usuario", "Eliminar usuario" y "Cancelar".

No parece muy complicado ¿verdad? De hecho existen muchas formas de implementarlo; el problema es que la mayoría de ellas son inaccesibles.

El objetivo de este artículo es explicar qué implementaciones son posibles y clasificarlas en:

NO accesible/NO nos sirveNO accesible/NO nos sirve

Accesible/Nos sirve pero NO es recomendableAccesible/Nos sirve pero NO es recomendable

Accesible y recomendable Accesible y recomendable ¡bingo! ¡usémoslo!

Nota: no entraremos en discusiones acerca de si la operativa de "Crear Usuario" y "Eliminar Usuario" deberían ser dos páginas diferentes, con un único botón "Aceptar", etc. Es un ejemplo para ilustrar el problema de los formularios con varios botones.

Implementar la acción por Javascript

Clasificación:NO accesible/NO nos sirveNO accesible/NO nos sirve

Un desarrollador novato (llamémosle Hugo) saldrá del paso de la siguiente manera:

<input type="button" 
value="Crear usuario" 
id="nuevo"
name="nuevo" 
onclick= "document.form1.action = 'nuevo.jsp'; 
document.form1.submit()" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar"
onclick= "document.form1.action = 'eliminar.jsp'; 
document.form1.submit()" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" 
onclick= "self.location.href = 'principal.jsp'" />

Si tenemos suerte y a Hugo se le ha estropeado el ratón, pulirá su código así:

<input type="button" 
value="Crear usuario" 
id="nuevo"
name="nuevo" 
onclick="document.form1.action = 'nuevo.jsp';
document.form1.submit()"
onkeypress="document.form1.action = 'nuevo.jsp'; 
document.form1.submit()" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar"
onclick="document.form1.action = 'eliminar.jsp'; 
document.form1.submit()" 
onkeypress="document.form1.action = 'eliminar.jsp';
document.form1.submit()" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" 
onclick="self.location.href = 'principal.jsp'" 
onkeypress= "self.location.href = 'principal.jsp'" />

Posiblemente Hugo, en un momento de lucidez, comprenda que si el primer botón es de tipo "submit", se convertirá en el botón por defecto, el que se ejecute al pulsar "intro" y además se ahorra una función.

Gracias a ello, un usuario que tenga desactivado el javascript o su user-agent no lo admita, al menos podrá crear un usuario:

<form action="nuevo.jsp"
name="form1"
id="form1"
method="post">      

[...]

<input type="submit" 
value="Crear usuario" 
id="nuevo"
name="nuevo" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar"
onclick="document.form1.action = 'eliminar.jsp';
document.form1.submit()" 
onkeypress="document.form1.action = 'eliminar.jsp';
document.form1.submit()" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" 
onclick="self.location.href = 'principal.jsp'" 
onkeypress="self.location.href = 'principal.jsp'" />

Si Hugo tiene muchos formularios, terminará descubriendo las funciones javascript y limpiando su código:

<script type="text/javascript">
<![CDATA[
function someter(id){
document.form1.action=id;
document.form1.submit();
}

function navegar(id){
self.location.href=id;
}
]]>

</script>
[...]

<form action="nuevo.jsp"
name="form1"
id="form1"
method="post">      

[...]

<input type="submit" 
value="Crear usuario" 
id="nuevo"
name="nuevo" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar"
onclick="someter('eliminar.jsp')" 
onkeypress="someter('eliminar.jsp')" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" 
onclick="navegar('principal.jsp')" 
onkeypress="navegar('principal.jsp')" />

Si Hugo asiste a un cursillo básico acerca de las bondades de separar la presentación, del contenido y de la programación, entonces quizás tengamos la suerte de ver javascript no-intrusivo:

[en el fichero funciones.js]
function init(){
document.getElementById("eliminar").onclick =  
function(){
document.form1.action= "eliminar.jsp";
document.form1.submit();
}

document.getElementById("eliminar").onkeypress =  
function(){
document.form1.action= "eliminar.jsp";
document.form1.submit();
}
document.getElementById("cancelar").onclick =  
function() {
self.location.href="navegar.jsp"; 
}
document.getElementById("cancelar").onkeypress = 
function() {
self.location.href="navegar.jsp"; 
}
}
window.onload=init;      


[en la página]

<form action="nuevo.jsp"
name="form1"
id="form1"
method="post">      

[...]

<input type="submit" 
value="Crear usuario" 
id="nuevo"
name="nuevo" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" />

En fin, podríamos seguir mejorando y depurando el código o poner otras alternativas similares que se ven a menudo, por ejemplo ir cambiando dinámicamente por javascript un campo "hidden" donde se guarda el destino final de la página, y mediante el evento "onsubmit" del formulario cambiar el "action" del formulario, etc.

Pero todas ellas nos llevarán a un callejón sin salida, porque un usuario que no tenga activado el javascript o su user-agent no admita javascript, en el mejor de los casos podrá ejecutar sólo la acción "Crear Usuario".

Varios formularios

Clasificación:NO accesible/NO nos sirveNO accesible/NO nos sirve

Hugo le ha pedido ayuda a Pedro, quien después de pensar un rato ha tenido una idea genial: hagamos que todos los botones sean de tipo "submit" y para evitar tener que modificar por javascript el "action" del formulario, ¡hagamos varios formularios!. Dicho y hecho.

Lo primero que se les ocurre es encerrar cada botón en un formulario:


<form name="form1" [...]
<input type="submit" [...]
</form>
<form name="form2" [...]
<input type="submit" [...] 
</form>
<form name="form3" [...] 
<input type="submit" [...]
</form>

Pronto se dan cuenta de que esto no lleva a ninguna parte porque el campo de texto NIF no está dentro del formulario y no se enviará su contenido con el "submit".

Pedro, que no se rinde fácilmente, decide que lo que hay que hacer es anidar los formularios:


<form name="form1" [...] 
<form name="form2" [...]
<form name="form3" [...]

[aquí nuestro campo de texto NIF]

<input type="submit" [...]
</form> 
<input type="submit" [...] 
</form> 
<input type="submit" [...] 
</form>

Pedro y Hugo comprueban que esto no funciona... lástima, no se pueden anidar formularios.

Alternativa accesible con <noscript>

Clasificación:Accesible/Nos sirve pero NO es recomendableAccesible/Nos sirve pero NO es recomendable

Hugo y Pedro recuerdan que Lisa hizo un curso de accesibilidad y le piden consejo. Ella lo ve claro: "utilicemos la etiqueta <noscript>". Lisa se pone de inmediato a teclear, añadiendo al final del código que vimos en el primer apartado el siguiente <noscript>:


<form action="nuevo.jsp"
name="form1"
id="form1"
method="post">      

[...]

<input type="submit" 
value="Crear usuario" 
id="nuevo"
name="nuevo" />

<input type="button"
value="Eliminar usuario"
id="eliminar"
name="eliminar" />

<input type="button"
value="Cancelar"
id="cancelar" 
name="cancelar" />

</form>

<noscript>
<p>Su navegador no admite javascript.</p>
<ul><li>Para crear un nuevo usuario pulse el botón 
"Crear usuario" del formulario anterior</li>
<li>Para cancelar y volver a la página anterior
pulse el siguiente enlace: 
<a href="principal.jsp">Cancelar</a></li>
<li>Para eliminar el usuario, rellene el
formulario que le presentamos a continuación:</li>
</ul>

<h2>Eliminar usuario</h2>

<form action="eliminar.jsp" method="post" 
name="form2" id="name="form2">

<label for="nifacc">NIF*:
<input id="nifacc" name="nifacc" />
</label>

<input type="submit" 
value="Eliminar usuario" 
id="eliminaracc"
name="eliminaracc" />

</form> 

</noscript>

Lisa les propone mostrar la página a varios compañeros, desactivándoles el javascript, para ver si les queda clara la forma de interactuar con el formulario. Estos compañeros ven la página así:

Ejemplo visual del formulario sin javascript activado

Pronto se dan cuenta de que puede que la página sea accesible, pero no resulta demasiado usable, parece que los usuarios son reacios a leer instrucciones. Después de intentar reiteradamente interactuar con los primeros botones y maldecir porque no funcionan, algunos se deciden a leer el texto explicativo. En el mejor de los casos han perdido mucho tiempo, en el peor se han rendido.

Por otro lado, una vez superada la euforia inicial, Pedro y Hugo empiezan a reflexionar sobre el sobreesfuerzo de programación y el aumento del peso de la página, esta solución empieza a ser poco práctica para implementar y mantener en todos los formularios de la aplicación, bastante más complejos que este.

La última razón para desechar esta opción se la proporciona Lisa: ups! se le olvidó comentar que la etiqueta "noscript" ya no es bien vista por el W3C y no estará en el estándar XHTML2.0

¿Y si utilizamos las etiquetas <a> o <button>?

Clasificación:NO accesible/NO nos sirveNO accesible/NO nos sirve

A estas alturas se ha extendido el rumor y es la comidilla de la oficina que Hugo, Pedro y Lisa no son capaces de crear un simple formulario compuesto de un campo y tres botones. Así que Gabi, a quien le atraen los marrones más que a una mosca, se deja caer por ahí.

"Si no podéis usar botones, ¿por qué no usáis etiquetas <a> que con el estilo simulen un botón? Mejor aún, ¿por qué no utilizáis <button>?"

<button> es una de esas etiquetas que apenas se usan, hasta tal punto que llegamos a dudar de que sea estándar. Pero lo es, incluso en la especificación XHTML 1.1.

Un <button> puede contener una etiqueta <a>, por tanto PARECE que es una ventaja utilizar <button> en vez de utilizar <a>: ya que tendrá aspecto de botón y se hundirá al pulsarlo sin necesidad de simularlo con estilos, además es un elemento de formulario y por ello PARECE más apropiado.

Y digo PARECE porque <button> en realidad no se utiliza debido a que no funciona correctamente en Explorer.

Pedro y Hugo tienen otras razones para no usar la alternativa de Gabi, bien con enlaces bien con botones "nos sirve para el botón 'Cancelar' porque sólo navega, pero con el resto de botones queremos someter la página, así que necesitaríamos añadir al enlace, al botón o al enlace dentro del botón un evento javascript que enviara el formulario, y si hacemos el botón de tipo 'submit' estaríamos también igual que antes".

Otro callejón sin salida.

XForm

Clasificación:NO accesible/NO nos sirveNO accesible/NO nos sirve

A estas alturas, Hugo, Pedro, Lisa y Gabi deciden que necesitan a Feder, el friki de la empresa, así que se dirigen en procesión a su mesa.

"XForm, esto se soluciona con XForm". Sus compañeros le miran con devoción, no tienen ni idea de lo que les habla, pero por lo visto están salvados.

XForm,- les explica Feder-, es recomendación oficial del W3C desde marzo de 2006, es la nueva generación de formularios que nos permite separar la presentación del contenido.




[en el modelo de datos]
<model>
[...]
<submission action="nuevo.jsp" method="post"
id="crear"/>
<submission action="eliminar.jsp" method="post"
id="eliminar"/>
<submission action="principal.jsp" method="post" 
id="cancelar"/>
[...]




[en el body]

[...]

<submit submission="crear">
<label>Crear usuario</label>
</submit>

<submit submission="eliminar">
<label>Eliminar usuario</label>
</submit>

<submit submission="cancelar">
<label>Cancelar</label>
</submit>

Feder les indica dónde pueden ver un ejemplo.

Al cabo de un rato regresan a la mesa de Feder: hemos probado con Firefox, con Explorer... pero no conseguimos que funcione.

Bueno, es que actualmente ningún navegador soporta XForm nativamente,- les explica,- pero existen plugins y extensiones para Explorer y Firefox.

Sus compañeros le hacen ver que no pueden obligar al usuario a utilizar determinado navegador y además instalarse un plugin o extensión.

Bueno,- insiste Feder- también se pueden convertir en el servidor, en tiempo de ejecución, a formularios de HTML, hay proyectos de código abierto.

También podéis echar un vistazo a AJAXForms,- añade Feder- que transforma documentos XHTML/XForms en páginas HTML con javascript, cuya comunicación con el servidor se restringe al intercambio de datos utilizando técnicas AJAX.

Sus compañeros se deshinchan... si transformamos los documentos XHTML/XForms en páginas HTML con javascript... ¿qué hemos ganado? Estamos otra vez en el punto de partida.

MVC

Clasificación:Accesible y recomendable Accesible y recomendable ¡bingo! ¡usémoslo!

Hugo les da las gracias a todos y decide enviar un mensaje a la lista de correo de la empresa pidiendo más ideas. No tarda en llegar la respuesta.


<form action="./UsuariosServlet"
name="form1"
id="form1"
method="post">      
[...]
<input type="hidden" 
value="Usuario" 
id="pageOperation"
name="pageOperation" />
<input type="submit" 
value="Crear usuario" 
id="evento_nuevo"
name="evento_nuevo" />
<input type="submit"
value="Eliminar usuario"
id="evento_eliminar"
name="evento_eliminar" />
<input type="submit"
value="Cancelar"
id="evento_cancelar" 
name="evento_cancelar" />
</form>


Limpio, transparente, fácil de mantener, 100% accesible... Dejemos que la aplicación, en el servidor, en función del valor del "pageOperation" y del botón pulsado (fácil de identificar porque su "name" comienza con "evento") decida qué acción realizar.


Importante:

  • No utilizar <button type="submit" ... en vez de <input type="submit" ... puesto que <button> funciona mal en Explorer y enviará todos los botones y por tanto no podremos saber en el servidor cuál se ha pulsado.
  • Si la página tienen validaciones javascript en función de las cuales se hace o no el "submit" (por ejemplo mediante un evento asociado al "onsubmit" del "form") siempre hay que realizar también estas validaciones en el servidor, puesto que al tener botones "submit", si el usuario no tiene javascript activo o su "user-agent" no lo soporta, el formulario se enviará porque se ignorará la validación javascript. Me parece muy interesante la reflexión que hace b-Make acerca de las páginas que contienen una cláusula legal que hay que aceptar para enviar el formulario.

NOTA: Toda la historieta de fondo es inventada con personajes ficticios, así me ha parecido más amena.


Artículos relacionados:

27 comentarios :
aNieto2k dijo...

Buenas Olga, me ha encantado el artículo. Una forma muy didáctica de acercarnos a la forma correcta de hacer las cosas...

No has comentado, y creo que tendrías que haber remarcado con mayusculas, que haciendo las cosas bien (caso MVC) te ahora líneas de código, tiempo y problemas.

Saludos.

xavi dijo...

muy didáctico. te has ganado un lector =)

Olga Carreras dijo...

Me voy de vacaciones.

Nos vemos a final de Agosto.

Álvaro dijo...

Genial artículo Olga, te doy la enhorabuena.

Anónimo dijo...

Muy buena olga, pero a mi me surge una duda, tengo un formulario con varios campos de texto y varios botones y quiero que cuando pulse enter se me seleccione el boton de al lado del texto en el que he escrito, es decir, cambiar el boton de envío por defecto, ya que alguno de mis botones tienen asociada una funcion, entonces no es solo por el envio de datos

Un saludo, Ignacio

Mol dijo...

Perfecto. Yo tenía el mismo problema y de verdad he ido pasando por esas fases más o menos en el mismo orden XD

Desde luego que con la historia que te has marcado se entiene mucho mejor

Hector dijo...

Hola, me gustaria saber si hay algun ejemplo para gestionar por parte del servidor que boton se pulsa del formulario.
Si pudieras indicarme alguna pagina con un ejemplo o algo sencillo por tu parte.
Un saludo y buen articulo

Anónimo dijo...

hola!
He estado buscando como mas no poder este cuento de tener varios botones en un formulario con jsp.
Ahora lo encuentro pero no lo entiendo, he intentado de todo!
Por eso te pido el favor de que hagas un ejemplo.
Gracias...

Anónimo dijo...

Yo trabajo con .net, y la verdad el codigo cambia bastante, aunque con los nuevos frameworks de JS toca volver a las bases del HTML y el JS, entendi bien la diferencia con tu articulo, muchas gracias..

Elena dijo...

me ha encantado, muy buena didáctica, estoy empezando a aplicar la accesibilidad y usabilidad en j2ee y me vienen muy bien estos ejemplos.

Anónimo dijo...

Magnifico documento, una manera amena y didactica de presentar parejas de problema/solucion, te felicito por ello.
Jose

zeus13 dijo...

Hola, grax por la info pero tengo unas dudas. Tambien te pediría que agregues un ejemplo.

Por qué el action="./Usuarios" ??

Cómo sabemos qué botón se presionó??

Anónimo dijo...

Típico, creas una buena historia pero dejas un final inconcluso

Olga Carreras dijo...

Para los últimos dos comentarios repito:

dejamos que la aplicación, en el servidor, en función del valor del "pageOperation" y del botón pulsado (fácil de identificar porque su "name" comienza con "evento") decida qué acción realizar.

cazaplanetas dijo...

Muy buen articulo... a mi me ha servido mucho.

Gracias.

Anónimo dijo...

hola olga!! muy buen articulo, te dire que soy muy nuevo en esto y tal vez te resulte muy boba mi pegunta jeje.. ¿podrias explicarme como me entero que boton pulse en mi aplicacion .php? ya se que aqui mencionas que con el "name" pero ¿podrias darme un pequeño ejemplo?

saludos!
muchas gracias por tu tiempo!!! :)

Planit dijo...

Hola, yo tengo una duda, como puedo hacer que un solo formulario entregue datos a varios sistemas, ejemplo, rellenas el formulario y envia datos a ingreso de pagina, formato de afiliado, newsletter.

Podrias ayudarme?

anjimgo@gmail.com

Olga Carreras dijo...

El ejemplo que ponía era con JSP, por eso Zeus13 pongo en el action "./Usuarios", es la llamada al servlet a ejecutar.

Para los que preguntan que cómo se programa la deteccción del botón pulsado, eso ya depende del lenguaje de programación que uses.

Siguiendo el ejemplo del servlet, lo más básico sería:

public class ServletAccesible extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

doAccesible(req, resp);

}

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

doAccesible(req, resp);

}

void doAccesible(HttpServletRequest req, HttpServletResponse resp) {

if (req.getParameter("aceptar")!=null) {

}
else if (req.getParameter("cancelar")!=null) {
}

}

}

A partir de ahí tendrías que trabajarlo para que fuera reaprovechable para toda la aplicación.

Anónimo dijo...

Hola, no me quedo claro si la operacion (nuevo, eliminar, cancelar) se guarda en el input hidden pageOperation. Si es asi ¿como se actualiza ese valor dependiendo de que boton se pulse? Si no es asi ¿que valor se guarda ahi?

saludos!

Anónimo dijo...

buen ejemplo

equicom dijo...

buen ejemplo

Anónimo dijo...

Sencillo y práctico.
Una forma de utilzarlo si desde el action del form llamamos a un PHP podría ser la siguiente:

$sBotonPulsado = '';
foreach ($_POST as $sPostInd => $postval) {
if (preg_match('/^evento_/', $sPostInd)) {
$sBotonPulsado = preg_replace('/^evento_/', '', $sPostInd);
}
}
if ($sBotonPulsado != '') {
// ...
}

Anónimo dijo...

weon enredao...bastaba con poner "esto es mejor que esto por tal motivo y listo"....

Anónimo dijo...

lo maximo seria bueno si tb explicaras como unir animaciones javafx en id netbeans para java web y softwares externos con los famosos metodos primitivos..
Saludos!

Anónimo dijo...

Muchísimas gracias!

Manuel Alejandro Gallego Reyes dijo...

Muchas Gracias, me sirvió de ayuda.., muy buena exposición

Anónimo dijo...

PERO QUIEN COÑO ES HUGOOOOO!?!??!?!!?!?!?!?!??!?!!?

Publicar un comentario en la entrada