Actualizar el contenido de un select con ajax.

En esta entrada vamos a explicar como asociar dos selects mediante ajax y al modificar el elemento seleccionado en uno de ellos cambie el contenido del otro.

Podemos ver el ejemplo de funcionamiento aquí.

Un ejemplo muy claro para esta situación sería dos selects, uno con provincias y el otro con localidades, lo que queremos es que al cambiar de provincia varíe la lista de localidades y muestre las que están en la provincia seleccionada.

Lo haremos utilizando el ajaxHelper, para que no sea necesario la recarga de la página y quede más atractivo.

Lo primero que haremos será definir los modelos de provincias ,localidades y un tercer modelo en el que usaremos los selects, por ejemplo podría ser un modelo de cliente, en el que al insertar un nuevo cliente tendríamos que elegir la provincia y localidad a la que pertenece.

Modelos

Modelo de provincia.

/app/models/provincia.php

class Provincia extends AppModel
{
    var $name = 'Provincia';
}

Modelo de localidad.

/app/models/localidade.php

class Localidade extends AppModel
{
    var $name = 'localidade';
}

Modelo de cliente.

/app/models/cliente.php

class Cliente extends AppModel
{
    var $name = 'Cliente';
}

Controladores

Después de esto tendremos que definir los controladores.

Controlador de localidad.

/app/controllers/localidades_controller.php

class LocalidadesController extends AppController
{
	var $name = 'Localidades';
}

Controlador de provincia.

/app/controllers/provincias_controller.php

class ProvinciasController extends AppController
{
	var $name = 'Provincias';
}

Y por últino el controlador de clientes que será el que implementará la funcionalidad.

Controlador de cliente.

/app/controllers/clientes_controller.php

class ClientesController extends AppController
{
	var $name = 'Clientes';
	var $helpers = array('Ajax');
	var $uses = array('Cliente','Provincia','Localidade');

	function insertar()
	{
		$listadoProvincias = $this->Provincia->find('all', array('fields'=>array('id','nombre'),'order'=>'nombre ASC'));
		$this->set('provincias', Set::combine($listadoProvincias, "{n}.Provincia.id","{n}.Provincia.nombre"));
		$primera_provincia = $this->Provincia->find(null,null,'nombre ASC');
		$listadoLocalidades = $this->Localidade->find('all', array('fields'=>array('id','nombre'),'order'=>'nombre ASC','conditions'=>'Localidade.provincia_id='.$primera_provincia['Provincia']['id']));
		$this->set('localidades', Set::combine($listadoLocalidades, "{n}.Localidade.id","{n}.Localidade.nombre"));

		// RESTO DE LA FUNCIONALIDAD DE INSERCIÓN DE CLIENTES
	}

	function update_select()
	{
		if (!empty($this->data['Localidade']['provincia_id']))
		{
			$provincia_id = $this->data['Localidade']['provincia_id'];
			$localidades = $this->Localidade->find('all', array('fields'=>array('id','nombre'),'order'=>'nombre ASC','conditions'=>array('provincia_id'=>$provincia_id)));
		}
		else
		{
			$localidades = $this->Localidade->find('all', array('fields'=>array('id','nombre'),'order'=>'nombre ASC'));
		}
		$this->set('options', Set::combine($localidades, "{n}.Localidade.id","{n}.Localidade.nombre"));
		$this->render('/elements/update_select', 'ajax');
	}
}

Por un lado tenemos la función insertar, que será una función genérica de inserción de clientes. La parte que a nosotros nos interesa es en la que se inicializan las variables que después utilizaremos en los selects, estas deberán ser arrays en los que cada elemento tenda un identificador y su valor, para que el select pueda utilizarlos en la vista.

En la versión 1.1 de cakephp, esto se podía hacer mediante la función generateList del modelo, función que se ha eliminado en la 1.2.
Por ello ahora es necesario hacerlo en dos pasos, por un lado realizar la búsqueda con un findAll y después utilizar la función combine de la clase Set que genera un array con la estructura deseada, que será el que le pasemos a la vista.

$listadoProvincias = $this->Provincia->find('all', array('fields'=>array('id','nombre'),'order'=>'nombre ASC'));
$this->set('provincias', Set::combine($listadoProvincias, "{n}.Provincia.id","{n}.Provincia.nombre"));

La segunda función update_select, es a la que llamará el ajax para actualizar el listado de localidades a partir de un identificador de provincia.

Como se puede ver en el código, la función coge el identificador de provincia del select y envía a la vista los arrays actualizados de provincias y localidades.

Vistas

Localidades y provincias no tendrán vistas asociadas, ya que no hay ninguna operación en el controlador.

En clientes tendremos que crear la vista para la operación insertar del controlador, esta podría ser algo como esto:

/app/views/clientes/insertar.ctp

echo $form->create('Inscripcione',array('action'=>'insertar'));
echo $form->inputs(array('legend'=>'Actualizar Provincias',
		'Localidade.provincia_id' => array('label'=> 'Provincia','showEmpty'=>'false','id'=>'provincias'),
		'Alumno.localidade_id' => array('label'=> 'Localidad','showEmpty'=>'false','id'=>'localidades'),
			));
echo $form->end();

$options = array('url' => 'update_select','update' => 'localidades');
echo $ajax->observeField('provincias',$options);

La vista será muy sencilla, un formulario con dos selects, uno de localidades y otro de provincias y una llamada al ajaxHelper en la que se indica que cada vez que se modifique el valor del select provincias, se llame a la función update_select y actualice el select de localidades.

Este select se actualizará con los valores devueltos por la vista de update_select, que lo único que hace es imprimir todos los nombres de localidades devueltos por la función del controlador.

/app/views/elements/update_select.ctp
if(!empty($options)) {
  foreach($options as $k => $v) {
    echo "<option value='$k'>$v</option>";
  }
 }

En este enlace se puede ver un ejemplo de funcionamiento.

29 Respuestas a “Actualizar el contenido de un select con ajax.”

  1. Julio Chapa escribió:

    Si, definitivamente el problema es que no se están inicializando las variables. Me podrías orientar un poquito en cuanto a eso?

  2. Julio Chapa escribió:

    Hola, ya solicioné el problema, me faltaba poner un $this->Usuario->id = $id; para que actualizara el id del usuario y por eso solo a veces me funcionaba. Muchas gracias por la ayuda!

    PS: Además tengo que solucionar un tema de Radio Buttons, cuando el usuario presione SI, se active un cambobox con datos y cuando el usuario presione NO, no ocurra nada. ¿Te ha tocado implementar algo asi? Se me ocurre poner un ajax que esté escuchando o un evento con javascript.

  3. Ale Parra escribió:

    Hola, tengo un problema con esto, me funcionó todo ok, pero cuando lo utilizo con una validación, hay un problema. Por ejemplo si selecciono una provincia y su localidad y luego por algún otro campo se dispara una validación, me queda seleccionada la provincia, pero la localidad se pierde. Alguna idea?

    Gracias.

  4. nuria escribió:

    Esto se debe a que el select se actualiza cuando se cambia la provincia, no en la carga de la página. Para corregirlo, tendrías dos posibilidades:
    1.- Llamar a una función ajax que cargue las localidades ante el evento onload.
    2.- Inicializar los valores de las localidades en el controlador: cuando en $this->data tenemos establecido el identificador de provincia, usamos éste para rellenarlo; en caso contrario, dependerá de la situación, o en función de una provincia inicial seleccionada desde el controlador, o bien de la que se encuentre almacenada en el modelo, en el caso de tratarse de una edición.

  5. miguel angel escribió:

    Hola, tengo un pequeñisimo problema con esto, NO ME MUESTRA NADA!… no entiendo, he hecho todo al pie de la letra, me he leido los 27 comentarios por si las moscas y nada…
    Me aparecen solamente 2 textBoxes en lugar de los 2 select’s que deberian aparecer… ya no se que hacer…
    ojala me puedas dar una mano…

  6. bernal escribió:

    Da la sensación de que las variables de datos que se le pasan a los selects están vacíos. Haz un debug de esas dos variables en la vista para ver si tienen datos y también ver si tienen la estructura correcta.

    Si no es por esto dame más información para hacerme una idea de que te puede estar pasando.

    Please continue discussion on the forum: link

[ bbPress synchronization by bobrik ]