JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Cómo permitir la desactivación de los botones de radio

Esto, que parece una tontería, no lo es en modo alguno. Cuando colocas un conjunto de botones de radio en una página Web con etiquetas de tipo <input type="radio">, al pulsar sobre cualquiera de ellos, éste queda seleccionado. Al pulsar sobre cualquier otro del mismo grupo, el primero se deselecciona ya que sólo uno de ellos puede estar seleccionado al mismo tiempo. Este es el comportamiento normal y esperado. Sin embargo hay un problemilla que mucha gente se encuentra que con sorpresa no pueden solucionar: ¿qué pasa si quieres desactivarlos todos una vez que ya hay alguno seleccionado?.

Prueba por ejemplo esta página. Al abrirla pulsa en los botones de radio de cualquiera de los grupos que contiene. Ahora intenta deseleccionarlos. No funciona.

Normalmente estos botones de radio se usan para dar a escoger entre varias opciones excluyentes entre sí, pero también puede darse la posibilidad de que la elección sea 'ninguna'. Lo que suele hacer la gente para solucionarlo es incluir un nuevo elemento en el grupo que sirva para indicar que no interesa ninguna de las otras, pero no deja de ser un apaño si lo que quieres en efecto es no tener ninguna seleccionada.

Si quieres solucionarlo puedes conseguirlo por código de una forma relativamente sencilla para un grupo de botones de radio. La solución consiste en llevar un registro del elemento que está actualmente seleccionado y desactivarlo por código si es éste el que se pulsa en un momento dado. No funciona la simple comprobación en el evento Click de si un elemento está seleccionado o no, ya que ese evento Click salta justo después e haberse hecho el cambio, por lo que no tienes forma de determinar si realmente estaba o no seleccionado (pruébalo, verás que es un poco frustante).

Aún así lo mas complicado es poder gestionar esta capacidad para una página completa independientemente de cuántos grupos de botones de radio existan en ésta.

Esto es precisamente lo que he hecho en esta otra página, para que el que se haya encontrado con este problema lo pueda solucionar. Si la visitas verás que en ella la desactivación de los botones de radio funciona perfectamente y que sólo es necesario pulsar en uno que esté seleccionado para que se desactive.

Para conseguirlo hace falta un buen puñado de JavaScript. Éste, además debe estar colocado después de todos los formularios de la página para que funcione. El motivo es que se realiza una inicialización dentro del código para la cual deben estar ya creados los contoles que se van a gestionar, y como sabemos los navegadores procesan los contenidos de las páginas a medida que se los encuentran.

A continuación se expone el código necesario. Éste usa algunas cuestiones interesantes, como la generación dinámica de variables en memoria así como la comprobación de la existencia de éstas. Vamos a analizarlo:

<script type="text/javascript">
//Para distinguir la opción actualmente pulsada en cada grupo
var pref_opcActual = "opcActual_";

//Verifica si una variable definida dinámicamente existe o no
function varExiste(sNombre)
{
    return (eval("typeof(" + sNombre + ");") != "undefined");
}

//Asigna un valor a una variable creada dinámicamente a partir de su nombre
function asignaVar(sNombre, valor)
{
    eval(sNombre + " = " + valor + ";");
}

//generamos dinámicamente variables globales para contener la opción pulsada en cada uno de los grupos
for (f= 0; f<document.forms.length; f++)
{
  for (i = 0; i< document.forms[f].elements.length; i++)
  {
    var eltoAct = document.forms[f].elements[i];
    var exprCrearVariable = "";
    if (eltoAct.type == "radio")
    {
      //Si la variable no existe la definimos siempre,pero sólo la redefinimos en caso de que el elemento actual del grupo esté asignado
      if (!varExiste(pref_opcActual + eltoAct.name) ) 
        exprCrearVariable = "var " + pref_opcActual + eltoAct.name + " = ";
      else
        exprCrearVariable = pref_opcActual + eltoAct.name + " = ";
      
      //El valor será nulo o una referencia al radio actual en función de su está seleccionado o no
      if(eltoAct.checked)
        exprCrearVariable += "document.getElementById('" + eltoAct.id + "')";
      else
        exprCrearVariable += "null";
        
      //Definimos la variable y asignamos el valor sólo si no existe o si el radio actual está marcado 
      if ( !varExiste(pref_opcActual + eltoAct.name) || eltoAct.checked) 
        eval(exprCrearVariable);
    }
  }
}


function gestionarClickRadio(opcPulsada)
{
  //El nombre de la variable que contiene el nombre del grupo actual
  var svarOpcAct = pref_opcActual + opcPulsada.name;
  
  var opcActual = null;
  
  opcActual = eval(svarOpcAct);  //recupero dinámicamente una referencia al último radio pulsado de este grupo
  
    if (opcActual == opcPulsada)
    {
        opcPulsada.checked = false; //deselecciono
        asignaVar(svarOpcAct, "null");  //y quito referencia (es como si nunca se hubiera pulsado)
    }
    else
    {
    asignaVar(svarOpcAct, "document.getElementById('" + opcPulsada.id + "')");  //Anoto la última opción pulsada de este grupo
    }
}
</script>

El prefijo pref_opcActual se usará para identificar la opción seleccionada en cada una de los grupos de controles existentes en la página. Defino dos funciones auxiliares, varExiste y asignarVar, que sirven para comprobar que una variable dada cualquiera existe en la página actual y para asignar dinñamicamente un valor a una variables. Lo interesante de estas dos funciones es que permiten la comprobación y la asignación pasando simplemente una cadena con el nombre de la variable que nos interesa, la cual puede existir o no, y no es encesario pasar una referencia real a dicha variable. Esta característica, habilitada por el método eval de JavaScript es muy interesante y no es utilizada muy a menudo por los programadores Web.

A continuación recorreo en un bucle cada uno de los posibles formularios que hay en la página (en ASP.NET uno solamente por regla general, pero esto funciona en cualqueir página) en busca de grupos de botones de radio.

Dentro del bucle para cada formulario lo que hago es generar dinámicamente, si no lo he hecho antes, el nombre de una variable por cada grupo de botones de radio. Ésta se encargará de almacenar dinámicamente una referencia al element actualmente seleccionado del grupo. Al terminar de ejecutarse el bucle lo que obtengo es una serie de variables llamadas opcActual_GrupoX, siendo GrupoX el nombre de cada grupo, las cuales contienen una referencia al elemento actualmente seleccionado en el grupo, de haberlo, o un nulo si no hay ninguno seleccionado en ese grupo.

Nótese que el nombre del grupo es el valor del atributo Name de cada elemento input, y que éste es el que se repite en los formularios apra defiinr los grupos. En un elemento input el atributo Name no tiene porque referirse realmente al nombre del elemento en la página (para eso está el atributo Id) sino que es el nombre que se recibe en eñ servidor una vez que se envía el formulario. En el caso de los botones de radio se envía sólo el valor de los que están seleccionados, por eso todo se pueden llamar igual 8sólo hay uno seleccionado a la vez y por lo tanto sólo se envía un valor por cada grupo. Este es código HTML para definir un grupo de radios tipo con dos elementos:

<input type="radio" id="01" name="grupo" value="Opción 1" onclick="gestionarClickRadio(this);"> Opción 1<br>
<input type="radio" id="o2" name="grupo" value="Opción 2" onclick="gestionarClickRadio(this);"> Opción 2<br>

Fíjate que en neustro ejemplo, como queremos que se puedan deseleccionar las opciones, he incluido un gestor del evento click, eso sí genérico, al que se le pasa simplemente una referencia al input que se ha pulsado (con this). Veaos qué hace este manejador del evento click (fíjate en el código JavaScript de arriba).

Lo primero es averiguar si hay alguna opción de este grupo que ya esté previamente seleccionada. Recuerda que no vale comprobar en este momento el valor de la propiedad checked del elemento que se pulsa, ya que su estado habrá cambiado y no tene os forma de saber cuál era el estado anterior. Ante esta limitación lo que se hace es ver el valor de la variable dinámica de tipo opcActual_ del grupo, que me devuelve una referencia al elemento que estaba seleccionado.

Si el elemento sobre el que hemo spulsado y a cuyo click estamos respondiendo coincide con el que teníamos registrado como seleccionado, entonces debemos desactivarlo. Además como ese es el único del grupo que podía estar pulsado, guardamos como elemento actualmente seleccionado un nulo, ya que ya no habrá ninguno (era este y lo acabamos de desactivar).

Si no era el actual el elemento pulsado entonces dejamos que la "naturaleza siga su curso" y que se quede marcado. Eso sí, anotamos el elemento actual como el seleccionado en nuestra variable opcActual_. Para ello evaluamos dinámicamente su identificador con getElementById (fíjate en que es la única forma de hacerlo y que n se pasa realmente una referencia al mismo a asignaVar, sino una cadena con la expresión que consigue evaluarlo).

Complicado ¿eh? :-)

Este es un excelente ejemplo de cómo una tarea aparentemente trivial puede ser bastante compleja de resolver y necesitaremos de técnicas de programación más o menos avanzadas para poder resolverla.

Échale un buen repaso al código, ejecútalo paso a paso con el depurador de Visual Studio y verás que hay una lógica sencilla debajo de todo esto.

¡Espero que te sirva!

José Manuel Alarcón
Banner

Agregar comentario