Crear un Dropdown dependiente usando AJAX.

Hola, había publicado antes en el foro pero al parecer lo hice en una sección equivocada y borraron mi Topic. Bueno, procedo a describir mi problema. Estoy creando una aplicación, en la cual uno de sus módulos se encarga del registro de estudiantes. El proceso de registro tiene en uno de sus pasos la selección de la ubicación del usuario (quizá ya sepan a donde quiero llegar). La ubicación se compone de tres tablas, una que contiene paises, otra que contiene provincias y otra que contiene localidades. El hecho es que el formulario de carga le pide al estudiante que escoja una localidad, pero como se podrán imaginar, para listar las localidades necesito primero seleccionar la provincia, para lo cual previamente necesito a su vez, seleccionar un país. Siguiendo esta guía Yii 1.1: Creating a dependent dropdown traté de replicar la estructura en mi aplicación, pero no he podido lograr que funcione, la carga del primer dropdown con los paises funciona perfectamente pero al querer cargar el dropdown con las provincias no funciona. He de admitir que soy un novato en el uso del framework y en PHP, por lo cual es probable que algo se me haya pasado por alto.

He aquí el código que uso en la vista:


<div class="row">

		<?php echo CHtml::label('Pais','paises_id',array ('required'=>true));?>

		<?php 

			$pmodels = Paises::model()->findAll();

			$data = array();

			foreach ($pmodels as $m)

			$data[$m->id] = $m->nombre;    

			echo CHtml::dropDownList('paises_id', '', $data ,

				array(

					'empty' => 'Seleccione su pais de procedencia',

					'ajax' => array(

						'type'=>'POST',

						'url'=>CController::createUrl('estudiantes/cargarprovincias'),

						'update'=>'#Paises_provincias_id',

						//'success' => 'function(data){$("select#provincias_id").html(data);}',

                		//'data' => array('paises_id' => 'js:$(this).val()')

						)

					)

				);

		?>

	</div>


	<div class="row">

		<?php echo CHtml::label('Provincia','provincias_id',array ('required'=>true));?>

		<?php echo CHtml::dropDownList('provincias_id','', array(), array('empty' => 'Seleccione su provincia de procedencia'),

				array(

					'ajax' => array(

						'type'=>'POST',

						'url'=>CController::createUrl('estudiantes/cargarlocalidades'), //url to call.

						'update'=>'#'.CHtml::activeId($model,'local_id'), //selector to update

						//'success' => 'function(data){$("select#local_id").html(data);}',

                		//'data' => array('provincias_id' => 'js:$(this).val()')

						)

					)

				);

		?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'local_id'); ?>

		<?php echo CHtml::dropDownList('local_id','', array(),array('empty' => 'Seleccione su localidad de procedencia'));?>

		<?php echo $form->error($model,'local_id'); ?>

	</div>



Y aquí el controlador:


	


public function accessRules()

	{

		return array(

			array('allow',  // allow all users to perform 'index' and 'view' actions

				'actions'=>array('index','view','cargarprovincias','cargarlocalidades'),

				'users'=>array('*'),

			),

			array('allow', // allow authenticated user to perform 'create' and 'update' actions

				'actions'=>array('create','update','cargarprovincias','cargarlocalidades'),

				'users'=>array('@'),

			),

			array('allow', // allow admin user to perform 'admin' and 'delete' actions

				'actions'=>array('admin','delete','cargarprovincias','cargarlocalidades'),

				'users'=>array('admin'),

			),

			array('deny',  // deny all users

				'users'=>array('*'),

			),

		);

	}


public function actionCargarprovincias()

	{

		$data=Provincias::model()->findAll('parent_id=:parent_id', 

                  array(':parent_id'=>(int) $_POST['paises_id']));

 

    	$data=CHtml::listData($data,'id','nombre');

    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

	}

	public function actionCargarlocalidades()

	{

		$data=Localidades::model()->findAll('parent_id=:parent_id', 

                  array(':parent_id'=>(int) $_POST['provincias_id']));

 

    	$data=CHtml::listData($data,'id','nombre');

    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

	}

}



Si alguien me pudiera dar una mano, la verdad es que me he quedado sin ideas, he probado con diferentes variaciones que he encontrado pero ninguna me ha dado resultado hasta ahora. Desde ya muchas gracias.

Saludos.

En primer lugar, lo de poner $pmodels = Paises::model()->findAll(); en una vista es una mala práctica. Esto debería estar en el controlador, y luego le pasas la variable a la vista.

Luego, creo que no te funciona porque has puesto ‘update’=>’#Paises_provincias_id’, y debería ser ‘update’=>’#provincias_id’,

Puedes utilitzar el firebug para comprobar que, cuando se cambia el valor del combobox de paises, realmente se esté llamando a actionCargarprovincias

Gracias moginn, es cierto, olvidé cambiar lo de #Paises_provincias_id, eso lo había hecho porque leí en una publicación de otro usuario que una alternativa era colocar el nombre del modelo antecediendo a la variable, ya lo regresé a como estaba antes, no obstante el error persiste. Usando Firebug he podido detectar que el error está en el controlador, cuando se llama a la función el fallo se produce en CDbCommand, al parecer no se hace el reemplazo de ‘parent_id’ por ‘paises_id’, sin embargo en la guía figura así, y aparentemente debería funcionar:




public function actionDynamiccities()

{

    $data=Location::model()->findAll('parent_id=:parent_id', 

                  array(':parent_id'=>(int) $_POST['country_id']));

 

    $data=CHtml::listData($data,'id','name');

    foreach($data as $value=>$name)

    {

        echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($name),true);

    }

}



¿Alguna pista de donde podría estar el error?

Listo, ya descubrí mi error, el problema era que en el controlador estaba asignando mal la variable:




public function actionCargarprovincias()

{

	$data=Provincias::model()->findAll('paises_id=:parent_id', 

             array(':parent_id'=>(int) $_POST['paises_id']));

 

    	$data=CHtml::listData($data,'id','nombre');

    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

}

public function actionCargarlocalidades()

{

	$data=Localidades::model()->findAll('provincias_id=:parent_id', 

             array(':parent_id'=>(int) $_POST['provincias_id']));

 

    	$data=CHtml::listData($data,'id','nombre');

    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

}



Ahora el problema es que no me carga el segundo dropdown. Al parecer ni siquiera se ejecuta la llamada a la función.

Lo que leiste es cierto si se está usando un CActiveForm por ejemplo:




<?php $form = $this->beginWidget('CActiveForm', array(

    'id'=>'user-form',

    'enableAjaxValidation'=>true,

    'enableClientValidation'=>true,

    'focus'=>array($model,'firstName'),

)); ?>

<?php echo $form->errorSummary($model); ?>

<div class="row">

    <?php echo $form->labelEx($model,'firstName'); ?>

    <?php echo $form->textField($model,'firstName'); ?>

    <?php echo $form->error($model,'firstName'); ?>

</div>

<div class="row">

    <?php echo $form->labelEx($model,'lastName'); ?>

    <?php echo $form->textField($model,'lastName'); ?>

    <?php echo $form->error($model,'lastName'); ?>

</div>

<?php $this->endWidget(); ?>

he revisado el código de actionCargarprovincias y parece correcto. Cuando dices que no te carga el segundo dropdown, ¿te refieres al dropdown de provincias o al de localidades?

a qué función te refieres, ¿actionCargarprovincias?

Perdón, mala mía, es el Dropdown de localidades.

He revisado el código de la vista y creo que lo tienes mal. Prueba con esto:




 <?php echo CHtml::dropDownList('provincias_id','', array(),

        array(

            'empty' => 'Seleccione su provincia de procedencia',

            'ajax' => array(

                    'type'=>'POST',

                    'url'=>CController::createUrl('estudiantes/cargarlocalidades'), //url to call.

                    'update'=>'#local_id', //selector to update

                    )

            )

        );

    ?>



Tenéis razón, tenía un "array()" demás. Ahora funciona, carga las localidades. Sólo me queda un inconveniente, no me toma el valor que selecciono del dropdown de localidades. Veré como soluciono eso. Muchas gracias.

Hola, aqui tienes una lista de ejemplos vivos ajax-based que hacen eso. http://yiiframeworkenespanol.com/ejemplo/combodependiente

Como para cerrar la idea y que le sirva a alguien que tenga el mismo problema, así quedó la implementación con un modelo:

En el controller:




public function actionCargarprovincias()

{

		$data=Provincias::model()->findAll('paises_id=:parent_id', 

                  array(':parent_id'=>(int) $_POST['Estudiantes']['paises_id']));

 

    	$data=CHtml::listData($data,'id','nombre');


    	echo CHtml::tag('option',

    			array('value'=>''), 'Seleccione su provincia de procedencia',true);


    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

}


public function actionCargarlocalidades()

{

		$data=Localidades::model()->findAll('provincias_id=:parent_id', 

                  array(':parent_id'=>(int) $_POST['Estudiantes']['provincias_id']));

 

    	$data=CHtml::listData($data,'id','nombre');


    	echo CHtml::tag('option',

    			array('value'=>''), 'Seleccione su localidad de procedencia',true);


    	foreach($data as $value=>$nombre)

    	{

        	echo CHtml::tag('option',

                   array('value'=>$value),CHtml::encode($nombre),true);

    	}

}



En la vista:




<div class="row">

	<?php echo $form->labelEx($model,'paises_id'); ?>

	<?php echo $form->dropDownList($model,'paises_id',

		CHtml::listData(Paises::model()->findAll(),'id','nombre'),

			array(

				'ajax'=>array(

				'type'=>'POST',

				'url'=>CController::createUrl('estudiantes/cargarprovincias'),

				'update'=>'#'.CHtml::activeId($model,'provincias_id'),

				'beforeSend' => 'function(){

                                       $("#Estudiantes_provincias_id").find("option").remove();

                                       $("#Estudiantes_localidades_id").find("option").remove();

                                }',  

				)

			)

		);?>

	<?php echo $form->error($model,'paises_id'); ?>

</div>


<div class="row">

	<?php echo $form->labelEx($model,'provincias_id'); ?>

	<?php 

        	$provincias_id = array();

                if(isset($model->provincias_id)){

            	$paises_id = intval($model->paises_id); 

            	$provincias_id = CHtml::listData(Provincias::model()->findAll("paises_id = '$paises_id'"),'id','nombre');

            }

		echo $form->dropDownList($model,'provincias_id',$provincias_id,

			array(

				'ajax'=>array(

				'type'=>'POST',

				'url'=>CController::createUrl('estudiantes/cargarlocalidades'),

				'update'=>'#'.CHtml::activeId($model,'localidades_id'),

				'beforeSend' => 'function(){

                                      $("#Estudiantes_localidades_id").find("option").remove();

                                      }',  

				)

			)

		);?>

	<?php echo $form->error($model,'provincias_id'); ?>

</div>



Así está funcionando perfectamente.

Saludos!