Main logo La Página de DriverOp

Listas dependientes.

Por Diego Romero.

En este artículo resolveré el problema de hacer que dos listas tipo select (o listbox) sea dependiente el segundo respecto del primero, es decir que el segundo select cambie de valores dependiendo del valor seleccionado en el primero.

Índice

1. Planteamiento del problema.

Para resolver este problema haré uso de PHP y JavaScript. La idea es que el usuario tenga dos elementos HTML tipo select o lista descolgable, el primero con una lista de valores y el segundo se cargará automáticamente con otros valores dependiendo del valor seleccionado por el usuario en el primero. Esto es lo que se llama en base de datos una relación "1 a muchos".

Primero necesitamos una fuente de datos, que puede ser el resultado de una consulta a una base de datos o cualquier otra fuente de datos que el programador disponga. Para propósitos de este artículo y a modo de ejemplo, usaré un archivo de texto el cual contiene todos los valores posibles que se cargarán en el segundo select. Los ejemplos mostrados aquí los presento de la forma más sencilla posible despreciando todo lo que tiene que ver con diseño HTML concentrándome únicamente en obtener la funcionalidad requerida, otros detalles se los dejo al criterio del lector. Usaré como ejemplo un caso típico: se trata de tener en el primer select una lista de países (tres en mi caso) y en el segundo una lista de províncias o estados que pertenecen a esos países, al seleccionar un país en el primer select se cargarán en el segundo solamente aquellos estados o províncias que pertenezcan al país seleccionado.

La página HTML.

Entonces, el estado inicial de la página HTML que contiene el formulario en cuestión sería así:

Formulario básico


Ejemplo de select dependientes
  


  

Aquí tenemos los dos select más un botón "Enviar" dentro de un formulario web, el primer select tiene la lista de países. Por sí mismo este formulario web no realiza la funcionalidad propuesta, me servirá como base para continuar el desarrollo de la solución paso por paso.

La ayuda de JavaScript.

El problema requiere que sea capaz de determinar qué valor ha seleccionado el usuario en el primer select para rellenar el segundo con los valores apropiados. Por lo tanto el siguiente problema a resolver es cómo determinar que el usuario ha seleccionado un país de la lista que está en el select "selector1". Para ello voy a recurrir al evento onChange el cual al ser disparado llamará a una función JavaScript.

Esta función debe determinar que el valor seleccionado es un valor válido y no el valor que le indica al usuario que seleccione un país, como se ve en el código HTML el primer valor que está por omisión no es nada más que un mensaje al usuario dándole la pista de lo que debe hacer, a esa entrada de la lista le he puesto un valor "null" que me ayudará a determinar si el usuario efectivamente ha seleccionado un país o no.

Entonces, en el primer select voy a asignar una función al evento onChange:

Evento onChange

    
    
    
    
  
  
  



Como adelanté al principio de este artículo el lector tendrá que implementar la recuperación de datos que crea conveniente en los lugares indicados en el código, en mi caso, como también mencioné ya, yo lo haré leyendo de un archivo de texto, lo que a continuación implemento será solamente a título demostrativo.

Rellenando el segundo select.

El archivo de texto en cuestión tiene un formato que "me inventé" para hacer la recuperación de datos más cómoda, haré fuerte uso de la función PHP explode(); la cual me permite dividir una cadena de texto usando un caracter especial como "token" y devuelve el resultado en la forma de un array. El formato del archivo tiene la siguiente sintaxis:

Formato del archivo de texto
Cod_ISO_país=Cod_Provincia:Nombre*Cod_Provincia:Nombre*Cod_Provincia:Nombre

Cada línea del archivo se corresponde con un país. Para leer extrayendo los datos de este archivo escribiré una función que tomará como parámetro el código ISO del país (y que está como valor en el select "selector1" y devolverá la línea de texto que le corresponde menos el código ISO (y el signo igual):

Este es el contenido del archivo "select2.txt":

Contenido del select2.txt
AR=BA:Buenos Aires*CB:Córdoba*ER:Entre Ríos
MX=DF:Distrito Federal*MI:Michoacán*MY:Monterrey
CO=DC:Distrito Capital*AT:Atlántico*AN:Antioquía

Y esta es la función:

Función GetContentSel2()
function GetContentSel2($sel) {
  $result = "";
  $found = FALSE;
  $fh = fopen("select2.txt","r");
  do {
    $aux = trim(fgets($fh));
    $aux = explode("=",$aux);
    if ($aux[0] == $sel) {
       $found = TRUE;
       $result = $aux[1];
    }
  } while (($found == FALSE) and (!feof($fh));
  fclose($fh);
  return $result;
}

La función espera como parámetro un código ISO de país de dos letras. Defino una variable vacía que será la que usaré como valor devuelto por la función, defino una variable bandera que me indicará si encontré o no el código dentro del archivo. Abro el archivo en modo lectura (el archivo debe existir!), inicio un cliclo do .. while, leo una línea de texto (la función PHP trim() me elimina aquí el caracter de fin de línea ya que ese caracter puede causar problemas de formato más adelante en el código HTML). Aplico explode() a la cadena leída la cual divide la cadena en el signo "=" resultando en dos cadenas que van a parar a la variable $aux (esta pasa de ser una variable string a un array con índice numérico), en la posición cero del array tengo el codigo ISO de país, el cual comparo con el parámetro de la función, en caso de ser igual, establezco a TRUE la variable que me indica que he encontrado el valor y asigno la variable de resultado con la segunda parte de la cadena (la que queda a la derecha del signo "="). Todo esto se repite hasta que o bién encontré lo que estaba buscando o bién llegué al final del archivo. Cierro el archivo y devuelvo el resultado.

¿Cómo sé que esta función encontró lo que estaba buscando?, porque en caso de no encontrar el país dentro del archivo devuelve una cadena vacía. La parte del código relevante queda como sigue:

Usando la función GetContentSel2()

$fillsel2 = FALSE; // esta es la variable bandera
$sel1 = ""; // esta variable debe estar definida
$request_method = $_SERVER["REQUEST_METHOD"];
if ($request_method == "POST") {
  $sel1 = @$_POST['selector1'];
  if (!empty($sel1) and ($sel1 != "null")) {
    $contentsel2 = GetContentSel2($sel1);
if (!empty($contentsel2)) { $fillsel2 = TRUE; }
} } // if reqmet ?>

El tinglado toma forma.

La variable $contentsel2 contendrá la línea con los datos para rellenar el select "selector2". El rellenado lo hago de la siguiente manera:

Armando el select "selector2"
  

Como habíamos visto antes, la variable $fillsel2 es la que me indica si debo o no debo llenar el select "selector2", uso explode para dividir la cadena $contentsel2 que es la que contiene la línea con las províncias del país, uso la estructura de control del lenguaje PHP foreach para recorrer el array resultante, cada posición del ahora array $contentsel2 debo dividirla a su vez en código de provincia y su nombre, que es lo que hago dentro del ciclo foreach devolviendo el array $item. Con los valores de $item escribo las cláusulas

Con esto tenemos nuestro problema resuelto. Cada vez que el usuario selecciona un valor en el select "selector1", el select "selector2" se carga con los valores correspondiendes al país seleccionado.

Hay un pequeño problema.

Pero, si el lector ha probado por su cuenta el código expuesto hasta aquí, habrá notado un pequeño inconveniente: cuando el usuario selecciona un país, la página se recarga, el select "selector2" toma los valores correctos, pero el select "selector1" regresa al valor por omisión sin importar qué país seleccionó previamente lo que puede ser confuso para el usuario. Esto se debe a la cláusula "selected" del tag "option" y si esa cláusula no está presente automáticamente muestra el primero. Sin embargo podemos usar esa cláusula a nuestro favor. Para ello usaremos la variable $sel1 que, si miran el código PHP al inicio del archivo, yo había señalado que esa variable debía estar definida. La variable $sel1 contiene el valor seleccionado previamente en el select "selector1" o ningún valor en caso que sea la primera vez que se carga la página. Con esta información es facil darse cuenta lo que hay que hacer: simplemente preguntar si $sel1 vale lo mismo que el valor correspondiente en cada

Arreglando el problema
  

La solución.

Ahora sí, el código completo:

La solución completa

function GetContentSel2($sel) {
  $result = "";
  $found = FALSE;
  $fh = fopen("select2.txt","r");
  do {
    $aux = trim(fgets($fh));
    $aux = explode("=",$aux);
    if ($aux[0] == $sel) {
      $found = TRUE;
      $result = $aux[1];
    }
  } while (($found == FALSE) and (!feof($fh)));
  fclose($fh);
  return $result;
}
$fillsel2 = FALSE;
$sel1 = "";
$request_method = $_SERVER["REQUEST_METHOD"];
if ($request_method == "POST") {
  $sel1 = @$_POST['selector1'];
  if (!empty($sel1) and ($sel1 != "null")) {
    $contentsel2 = GetContentSel2($sel1);
	if (!empty($contentsel2)) {	$fillsel2 = TRUE; }
  }
} // if reqmet
?>


Ejemplo de select dependientes




El ejemplo funcionando puede ser probado aquí.

Algunas preguntas que pueden surgir:

¿Qué pasa cuando el usuario hace click en "Enviar"?.

Pues que los datos del formulario van a parar al script "recibe.php".

¿Pero y si el usuario no seleccionó nada?.

En ese caso en el script "recibe.php" tendrás que verificar que los datos sean correctos. El mecanismo implementado acá no garantiza que los datos sean correctos, es simplemente para hacer la interfaz más amigable al usuario, más intuitiva. Aunque sí lleva implícita cierta validación, en el sentido de que previene que en el segundo select haya valores que no se correspondan con lo que dice el primero. Pero aún así siempre se debe tener en cuenta que "nunca debe confiarse en los datos que proporciona el usuario".

¿De dónde salen los valores del primer select?.

En el ejemplo que expongo en este artículo esos valores están "hardcodeados", pero no veo problema en que esos valores se carguen desde una base de datos también, las condiciones para hacerlo están implícitas en el ejemplo.

¿Se puede prescindir del botón "Enviar"?.

Yo creo que sí, basta con implementar una segunda función JavaScript que se ejecute en el evento onChange del select "selector2" muy similar a la implementada en mi ejemplo. Aunque esto a veces no es deseable porque no da oportunidad al usuario a corregir ("error de dedo" como suele decir mi socio :P).

No me gusta que la página se recargue cada vez que se selecciona un item del select "selector1".

Para evitar la recarga puedes implementar dos soluciones, una fácil y chapucera y una difícil y elegante. La primera es ¡carga todos los valores posibles (todos los países) en un array en JavaScript y que la selección se haga en el cliente y no en el servidor!, pero si haces eso corres el riesgo de hacer la página excesivamente pesada. En mi ejemplo no se notaría ya que hay 9 valores posibles (3 por cada país), pero supongamos que tenemos 40 países con un promedio de 30 provincias por país ¡¡son 1200 valores!!. La solución elegante es implementar AJAX que es una técnica de programación web asincrónica (la solución que presento aquí es sincrónica), pero te prevengo que implementar AJAX es tortuoso e implica conocer muy bien el lenguaje JavaScript. En la mayoría de los casos no vale la pena. En fin, es tu decisión.

Diego Romero -

Comentarios

Agregar comentario

190.18.196.113

Martes, 05 de Febrero de 2013 a las 09:27:18

Por: Diego Romero

Karenjs:

  Tu pregunta escapa al propósito de este artículo porque no estás hablando de listas dependientes.

  Plantea la pregunta en Cristalab.

190.204.178.30

Martes, 05 de Febrero de 2013 a las 02:25:35

Por: Karenjs

Estuve leyendo un poco mas, a lo que me referia es que una caja de texto sea dependiente de un select pero que dicho select sea dinamico, es decir que los datos del select sean extraidos de la bd. Por ejemplo una tabla cursos con los campos nombre_curso, precio. Primero extraer con un select los nombres del curso (eso si se, como es) y luego de acuerdo a lo seleccionado que coloque o muestre el precio en una caja de texto. Manualmente puede hacerse usando condicionales o una funcion que compare, sin embargo la idea es que sea dinamico porq se puede tener n cursos y los precios podrian cambiar cada cierto tiempo, creo que es mas comodo y menos tedioso sin embargo No he visto ningun ejm con php parecido, sin embargo revisare las funciones de javascript a ver que consigo.

190.18.196.113

Martes, 08 de Enero de 2013 a las 16:25:02

Por: Diego Romero

Hola Karenjs:

 Será cuestión de adaptar el código a tus necesidades. Yo no veo impedimento pero no tengo más detalles que los que tú me has dado.

 Gracias por comentar.

190.204.178.30

Martes, 08 de Enero de 2013 a las 13:38:07

Por: Karenjs

Gracias por esta publicación. ¿Es igual el proceso si quiero que los datos aparezcan en una caja de texto? Por ejemplo el precio, seleccionar articulo y que el precio aparezca en la caja de texto.

190.229.195.226

Sábado, 30 de Junio de 2012 a las 19:26:12

Por: Diego Romero

Leandro:

 El concepto es el mismo con tres, cuatro, cinco o los que sean.

Por preguntas más generales, en los foros de CritaLab por favor.

190.194.89.134

Sábado, 30 de Junio de 2012 a las 18:30:58

Por: Leandro

Que tal. Me sirve mucho este tutorial. Pero lo que yo quiero hacer, es con TRES Select menu. Es decir, uno mas.. podrias darme una mano con esto que es de suma necesidad? Gracias

190.228.219.236

Domingo, 06 de Mayo de 2012 a las 01:57:19

Por: Diego Romero

Ervin:

 Los datos salen del archivo "select2.txt" que no es más que un archivo de texto que se parsea con la función GetContentSel2(), como se explica en la sección "Rellenando el segundo select" del artículo.

190.148.63.81

Sábado, 05 de Mayo de 2012 a las 23:25:23

Por: Ervin

Holas, primero que nada quiero agradecer el tiempo que te tomaste en explicar este ejemplo,  y aunque no tiene etiqueta si es para avanzados intermedios, al final eso no importa, 1 si te interesa tienes que investigar, 2 si no entiendes y tienes dudas pregunta.

ahora a mi me resulta un poco la duda de donde sacas la información para el segundo select.

saludos y buen aporte.

190.99.255.73

Jueves, 03 de Mayo de 2012 a las 23:03:12

Por: Cutero

Gracias, de los selects dependiente mas sencillos...

190.158.135.108

Domingo, 29 de Abril de 2012 a las 20:41:56

Por: Carolina Gallo

Muchas gracias, tu código me ayudó a resolver muchas dudas que tenía, no solo de lo que buscaba sino que despejó más de las que esperaba, eres muy amable.

186.46.139.214

Viernes, 20 de Abril de 2012 a las 18:15:16

Por: Alexander P

Gracias por tu aporte, sigue posteando mas cosas y contenidos, el conocimiento es libre y todos los aportes son necesarios, todas las soluciones son bienvenidas, por favor dejar negativismos y crear.

186.124.128.15

Sábado, 14 de Abril de 2012 a las 09:18:21

Por: Diego Romero

Samuel:

  Si supieras lo suficiente de PHP, además de prestar atención a lo que lees, sabrías que la respuesta tus cuestiones están ahí y que en PHP no existe eso de "ir configurado para". Eso no tiene sentido.

201.156.213.119

Viernes, 13 de Abril de 2012 a las 18:03:19

Por: Samuel Salazar

Si supiera lo suficiente no entraria aqui a checar tu tutorial y perder el tiempo, creo que  si quieres enseñar deberias de ser un poco mas explicito para novatos intermedios y avanzados aqui no dice que solo para avanzados es esto creo que los avanzados no te van a poner atencion son autosuficientes y disculpa mi comentario si te incomoda

190.18.198.252

Viernes, 13 de Abril de 2012 a las 15:17:03

Por: Diego Romero

Samuel:

 Tu comentario me hace pensar que no sabes lo suficiente de PHP.

201.156.213.119

Viernes, 13 de Abril de 2012 a las 03:08:23

Por: Samuel Salazar (c561283@hotmail.com)

Se me hace un ejemplo muy malo didacticamente por que me deja 2 dudas no explica como debe ir configurado el archivo result.php y el  archivo recibe.php ni explica la forma que debe ir configurado para que se cargue en una base de datos.

190.18.198.252

Jueves, 02 de Febrero de 2012 a las 14:25:03

Por: Diego Romero

Superpeyo:

 Gracias por el comentario.

Después de la portada, esta es la página que más visitas tiene, lo sé porque aunque no aparezca un contador sí tengo uno para cada página del sitio. Otra vez gracias por el apoyo ;).

201.239.202.150

Jueves, 02 de Febrero de 2012 a las 01:16:47

Por: Superpeyo (superpeyo@gmail.com)

Me llama mucho la atencion que nadie te de las gracias por el aporte,  te aseguro que esto sirve mucho , aqui lo explicas de forma sencilla , detallada y no es tan facil de encontrar.Se que la intencion no es que te veneren o rindan pleitecia, pero me molesta que personas se beneficien con el trabajo ajeno sin dar las gracias, coloca un contador (si es que se puede)y te daras cuenta de cuantas personas han visto este post.

Asi que Gracias,  el ejemplo me funciono a la primera y simplemente copie y pege.

Atte Pedro.

189.250.169.124

Miércoles, 11 de Enero de 2012 a las 20:41:20

Por: Daniel

Hola como estas

Comentario irrelevante

190.228.220.20

Domingo, 08 de Enero de 2012 a las 13:30:35

Por: Diego Romero

Lisseth:

Salen del archivo de texto, tal como se explica en el propio artículo con lujo de detalles.

200.85.30.229

Domingo, 08 de Enero de 2012 a las 13:19:50

Por: Lisseth Cruz

¿De donde Salen los valores del segundo select? 

Arriba
1
cerrar
Espera un momento...
Cargando...
Ups!, algo anda mal.