How to create dependent drop down list in nested dynamic form in Yii 2

I am using Yii 2 basic application template. I am using dynamic form widget in Yii 2. I am using nested dynamic form scenario for creating the form.

The user selects the ASC from the drop down and selects the time and adds many students details for each time. I want to create a dependent drop down where when the user selects the ASC from the drop down list then students names should be changed in the corresponding student drop down field. I have created a function which displays the student names based on selecting the ASC

public function actionStudentLists($id)
    {
        $countstudents = Student::find()->where(['ASCId' => $id])->count();

        $Students = Student::find()->where(['ASCId' => $id])->all();

        if ($countstudents > 0) {

			echo "<option value>Select Students</option>";

            foreach ($Students as $Student) {
					ob_start();

                echo "<option value='" . $Student->StudentId . "'>" . $Student->StudentName . "</option>";
            }
        } else {
            echo "<option> - </option>";
        }
    }

form

<?php
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use yii\jui\DatePicker;

use wbraganca\dynamicform\DynamicFormWidget;
use app\models\Ascassignment;
use app\models\Asccenter;
use kartik\time\TimePicker;
use yii\helpers\ArrayHelper;
use app\models\Student;


/* @var $this yii\web\View */
/* @var $model app\models\Ascteacherreport */
/* @var $form yii\widgets\ActiveForm */
?>

<script>


$(document).ready(function() {
  $(window).keydown(function(event){
    if(event.keyCode == 13) {
      event.preventDefault();
      return false;
    }
  });
});

$("#dynamic-form")[0].reset();

</script>

<div class="ascteacherreport-form">

   <?php $form = ActiveForm::begin(['id' => 'dynamic-form',  'options' => ['class' => 'disable-submit-buttons'],
   ]);?>

   <div class="panel panel-primary " >
<div class="panel panel-heading"><font size="3"><b>Teacher Report</b></font></div>
<div class="row">
<div class="col-sm-4">

    <?= $form->field($model, 'ASCId')->dropDownList(ArrayHelper::map(Asccenter::find()->leftJoin('ascassignment','`ascassignment`.`ASCId`=`asccenter`.`ASCId`')->where(['ascassignment.UserId' => \Yii::$app->user->identity->getonlyid()])->all(),'ASCId','ASCName'), ['prompt' => 'Select ASC Center','onChange' => '


	$.post("index.php?r=student/student-lists&id=' . '"+$(this).val(),function(data){
						

                    });']) ?>
	</div>

<div class="col-sm-4">

	<?= $form->field($model, 'DateofReport')->widget(DatePicker::classname(), [
                                            //'language' => 'ru',
                                            'dateFormat' => 'yyyy-MM-dd',
											'options' => ['class' => 'form-control picker','readOnly'=>'readOnly'],
											'clientOptions'=>['changeMonth'=>true,
                                            'changeYear'=>true,
                                            'readOnly'=>true]

]) ?>
</div>
	</div>
	</div>



<div class="panel panel-primary">
        <div class="panel-heading"><font size="3"><b>Time and Student Details</b></font></div>
<?php DynamicFormWidget::begin([
        'widgetContainer' => 'dynamicform_wrapper',
        'widgetBody' => '.container-items',
        'widgetItem' => '.time-item',
        'limit' =>10,
        'min' => 1,
        'insertButton' => '.add-time',
        'deleteButton' => '.remove-time',
        'model' => $modelsTime[0],
        'formId' => 'dynamic-form',
        'formFields' => [
            'Time',
        ],
    ]); ?>
    <table class="table table-bordered">
        <thead>
            <tr bgcolor='#B8B8B8'>
			<th style='border: 1px solid black;'></th>
               <th class ="text-center" style='border: 1px solid black;'>Time</th>
                <th class ="text-center" style='border: 1px solid black;'>Student Details</th>
                <th class="text-center" style='border: 1px solid black;'>
                    <button type="button" class="add-time btn btn-success btn-xs"><span class="fa fa-plus"></span> Add Time</button>
                </th>
            </tr>
        </thead>
        <tbody class="container-items">
        <?php foreach ($modelsTime as $indexTime => $modelTime): ?>
            <tr class="time-item">
                <td class="vcenter" style='border: 1px solid black;'>
                    <?php
                        // necessary for update action.
                        if (! $modelTime->isNewRecord) {
                            echo Html::activeHiddenInput($modelTime, "[{$indexTime}]ASCReportDetailsId");
                        }
                    ?>
					</td>
					<td style='border: 1px solid black;width:15%'>
                    <?php

					if (! $modelTime->isNewRecord)
					{
					$time1 = explode('To',$modelTime->Time);
					$fromtime=$time1[0];
					$totime = $time1[1];

					$modelTime->FromTime = $fromtime;
					$modelTime->ToTime = $totime;


					 echo $form->field($modelTime, "[{$indexTime}]FromTime")->label(true)->widget(TimePicker::classname(),[

    'pluginOptions' => [
        'readOnly' => true,
		'minuteStep' => 1,
    ],
]);

					echo "<br>";
					echo $form->field($modelTime, "[{$indexTime}]ToTime")->label(true)->widget(TimePicker::classname(),[

    'pluginOptions' => [
        'readOnly' => true,
		'minuteStep' => 1,
    ],
]);
					}
					else
					{
						echo $form->field($modelTime, "[{$indexTime}]FromTime")->label(true)->widget(TimePicker::classname(),[

    'pluginOptions' => [
        'readOnly' => true,
		'minuteStep' => 1,
    ],
]);
	echo "<br>";

	echo $form->field($modelTime, "[{$indexTime}]ToTime")->label(true)->widget(TimePicker::classname(),[

    'pluginOptions' => [
        'readOnly' => true,
		'minuteStep' => 1,
    ],
]);

					}?>



                </td>

                <td style='border: 1px solid black;'>
                    <?= $this->render('_form-studentdata', [
                        'form' => $form,
                        'indexTime' => $indexTime,
                        'modelsStudentdata' => $modelsStudentdata[$indexTime],
                    ]) ?>


                </td>


                <td class="text-center vcenter" style='border: 1px solid black;'>
                    <button type="button" class="remove-time btn btn-danger btn-xs"><span class="fa fa-minus"></span></button>
                </td>
            </tr>
         <?php endforeach; ?>
        </tbody>
    </table>
    <?php DynamicFormWidget::end(); ?>

</div>








    <div class="form-group">
         <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
        <?= Html::resetButton('Reset',['class' => 'btn btn-default'])?>
    </div>

    <?php ActiveForm::end(); ?>

</div>

Inner student form

<?php

use yii\helpers\Html;
use yii\helpers\ArrayHelper;
use yii\bootstrap\ActiveForm;
//use nex\datepicker\DatePicker;
use yii\web\UploadedFile;
use wbraganca\dynamicform\DynamicFormWidget;
use app\models\Student;
?>




<?php DynamicFormWidget::begin([
    'widgetContainer' => 'dynamicform_student',
    'widgetBody' => '.container-student',
    'widgetItem' => '.student-item',
    'limit' => 50,
    'min' => 1,
    'insertButton' => '.add-student',
    'deleteButton' => '.remove-student',
    'model' => $modelsStudentdata[0],
    'formId' => 'dynamic-form',
    'formFields' => [
        'StudentId',
		'Subject',

		'Topic',
		'Confidence',
    ],
]); ?>
<table class="table table-bordered">
    <thead>
        <tr bgcolor='#B8B8B8'>
            <th style='border: 1px solid black;'></th>
                <th class ="text-center" style='border: 1px solid black;'>Student</th>
                <th class ="text-center" style='border: 1px solid black;'>Subject</th>
				<th class ="text-center" style='border: 1px solid black;'>Topic</th>
								<th class ="text-center" style='border: 1px solid black;'>Confidence</th>


            <th class="text-center" style='border: 1px solid black;'>
                <button type="button" class="add-student btn btn-success btn-xs"><span class="glyphicon glyphicon-plus"></span> Add Student</button>
            </th>
        </tr>
    </thead>
    <tbody class="container-student">
    <?php foreach ($modelsStudentdata as $indexStudent => $modelStudentdata): ?>
        <tr class="student-item">
            <td class="vcenter" style='border: 1px solid black;'>
                <?php
                    // necessary for update action.
                    if (! $modelStudentdata->isNewRecord) {
                        echo Html::activeHiddenInput($modelStudentdata, "[{$indexTime}][{$indexStudent}]ASCTeacherReportTimeDetailsId");
                    }
                ?>
				</td>
				<td style='border: 1px solid black; width:30%'>
                <?= $form->field($modelStudentdata, "[{$indexTime}][{$indexStudent}]StudentId")->label(false)->dropDownList(ArrayHelper::map(Student::find()->leftJoin('asccenter','`asccenter`.`ASCId`=`student`.`ASCId`')->leftJoin('ascassignment','`ascassignment`.`ASCId`=`asccenter`.`ASCId`')->where(['ascassignment.UserId' => \Yii::$app->user->identity->getonlyid()])->all(),'StudentId','StudentName'), ['prompt' => 'Select Student']) ?>
            </td>
			<td style='border: 1px solid black; width:30%'>
                <?= $form->field($modelStudentdata, "[{$indexTime}][{$indexStudent}]Subject")->label(false)->textInput(['maxlength' => true]) ?>
            </td>
			<td style='border: 1px solid black; width:30%'>
                <?= $form->field($modelStudentdata, "[{$indexTime}][{$indexStudent}]Topic")->label(false)->textInput(['maxlength' => true]) ?>
            </td>

			<td style='border: 1px solid black; width:30%'>
                <?= $form->field($modelStudentdata, "[{$indexTime}][{$indexStudent}]Confidence")->label(false)->textInput(['maxlength' => true]) ?>
            </td>
            <td class="text-center vcenter" style=" border: 1px solid black;width: 90px;">
                <button type="button" class="remove-student btn btn-danger btn-xs"><span class="glyphicon glyphicon-minus"></span></button>
            </td>
        </tr>
     <?php endforeach; ?>
    </tbody>
</table>

<?php DynamicFormWidget::end(); ?>


I suggest you look for Select2 and listen to change event and then clear/load the second selection list. That can be done easily with Kartik Select2 implementation.

Pay attention to plugin events

We can also use normal dependent drop down. I have the function StudentList() which returns all the students for selected ASC as shown above. So just need to populate each student field for each time row with the student names returned by the function.

<?= $form->field($model, 'ASCId')->dropDownList(ArrayHelper::map(Asccenter::find()->leftJoin('ascassignment','`ascassignment`.`ASCId`=`asccenter`.`ASCId`')->where(['ascassignment.UserId' => \Yii::$app->user->identity->getonlyid()])->all(),'ASCId','ASCName'), ['prompt' => 'Select ASC Center','onChange' => '


	$.post("index.php?r=student/student-lists&id=' . '"+$(this).val(),function(data){
						

                    });']) ?>

So as shown in the above figure, when a user selects ASC center then for all the time rows and for each student, student names should be populated/filled in. Even if the user clicks on new time row and adds new student then also selected ASC center’s students should be displayed.

you mean setting array of data from server?
Like this SO question?

yes, same selecting ASC center from the drop down, upon selecting ASC center the ASC id will be used to find the students belonging to that center using StudentList() function and then each student row for each time row in the dynamic form should be populated with those particular students. (We need to fill each Student control / input for each time row with the Student names returned from the StudentList())

Then you can copy relevant code from example. it is pretty self explanatory. If you are having hard time just appending the data, check out this SO question. Have all you need

But I find Select2 having intuitive API

Choice is yours!

How to find how many time row are present on the form and for each time row how many student forms are present and how to loop through each student?

Similar to the below post

Why do you want to do that on client side? You can calculate that info on controller and pass them as part of data to the view. If you insist, check JQuery’s each

https://api.jquery.com/each/

I have assigned a class for StudentId field as below in the nested dynamic form

<?= $form->field($modelStudentdata, "[{$indexTime}][{$indexStudent}]StudentId")->label(false)->dropDownList(ArrayHelper::map(Student::find()->all(),'StudentId','StudentName'), ['prompt' => 'Select Student','class'=>'form-control i']) ?>

In the parent dynamic form I used the below code where the user selects a ASC center then the students are returned for that ASC center and gets filled for every student in the nested dynamic form for each time row. But whenever I add new student and new time then all the previous selected students gets cleared. I am using each function of jQuery which populates each student field with the students returned from the selection of ASC center.

<?= $form->field($model, 'ASCId')->dropDownList(ArrayHelper::map(Asccenter::find()->leftJoin('ascassignment','`ascassignment`.`ASCId`=`asccenter`.`ASCId`')->where(['ascassignment.UserId' => \Yii::$app->user->identity->getonlyid()])->all(),'ASCId','ASCName'), ['prompt' => 'Select ASC Center','class'=>'form-control ascid','onChange' => '

$.post("index.php?r=student/student-lists&id=' . '"+$(this).val(),function(data){
                    $(".i").each(function(k,v)
			         {
                            $(".i").html(data);
					 });

		           $(".dynamicform_student").on("afterInsert", function(e, item) {
                                 $( ".i" ).each(function() 
                                 {
                                    $(".i").html(data);
                                 });
                             });

	              $(".dynamicform_wrapper").on("afterInsert", function(e, item) {
                           $( ".i" ).each(function() {
                                   $(".i").html(data);
                                   });
                            });
                         });
					'


	]) ?>

What are these supposed to accomplish?

dynamicform_student is the nested child form. Whenever user adds new student (afterInsert) then each student field should be populated with those students for which ASC center is selected

dynamicform_wrapper - is the time form. Whenever user adds new time, then for each student of that time student field should be populated with those students for which ASC center is selected.

But I think these two are not required.

If I use the below code then only the first student row of the first time gets populated with those students for selected ASC. Other shows all the students.

<?= $form->field($model, 'ASCId')->dropDownList(ArrayHelper::map(Asccenter::find()->leftJoin('ascassignment','`ascassignment`.`ASCId`=`asccenter`.`ASCId`')->where(['ascassignment.UserId' => \Yii::$app->user->identity->getonlyid()])->all(),'ASCId','ASCName'), ['prompt' => 'Select ASC Center','class'=>'form-control ascid','onChange' => '

	$.post("index.php?r=student/student-lists&id=' . '"+$(this).val(),function(data){
                     $(".i").each(function(k,v)
					{
                          $(".i").html(data);
					}
					);
                    });

					'
	]) ?>

Am afraid I do not have enough time to look at such complex forms.
I hope you find help. But if you dont, try to break the process into multiple actions to avoid doing too much on Client side. I have always found not that funny unless I’m using JS framework for front end.

As shown in the above image when the user selects ASC as Laltaki then only those students are getting displayed in Student row for example John.

Now when I click Add Student to add new student for that time then all centers students are getting displayed which should be not happen

So even if the user clicks on Add Time to add new time and add students for that particular time, then also those students should be displayed as above for Laltaki.

In that case use Ajax to render the form. In that case renderAjax is your friend.

Here is pseudocode

function actionForm(){
    return $this->render('main-view-with-dropdown', [...] );
}

function actionRenderAsc($id){
    
    $model = [...]
    return $this->renderAjax('as-related-form', ['model' => $model]);
}

in main-view-xxx listen to change event and call ajax to populate the form

//views
$('#something').on('change', function(){
    $.get('/controller/render-asc?id='+idFromChangeEvent, ... function(form){
        $('#some-div-in-main').html(form);
    });
});

Hope that gives you an idea

Will I need to change the whole structure of the program

I can offer consultancy on restructuring at a coffee fee if you need it!

There must be some means using each function of jQuery which can populate all the nested forms using with the students data upon selection of ASC center

Rather, find a way of re-rendering the same form with different information based on selection. That allows you to use simplicity of Yii in doing actual rendering.

Unless you are front end developer, you don’t want your app to be doing a lot on client. If that be the goal, using framework like Vuejs with Yii2 API is ideal!