To Time is not getting fetched in nested dynamic form in Yii 2

I am using Yii 2 and dynamic form widget in my application.

I have created a form where user selects the time (From time and To time) and fills the student details for each time using nested dynamic form.

In the table I have field as Time, but in the model I have used two variables FromTime and ToTime in such a way that while saving the record to the database I joined these two variables and then assign them to Time variables.

Everything is working correctly, but in the update part the time which is coming from the table I use explode function so that I can get From Time and To Time and assign them to the widget appropriately.

But From Time is getting displayed correctly but in the To Time 12:00 AM is displayed. To Time is not getting fetched properly.

I am using yii2-widget-timepicker extension

Below is the code,

<?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; ?>

can you post also screenshots that explain the problem?

The above image shows the form when a user wants to update the report details.

For this report id, there are 3 times record in the table i,e.
10:00 AM To 11:00 AM, 11:00 AM To 12:00 PM and 12:00 PM To 01:00 PM

From Time is getting fetched correctly i,e 10:00 AM for the first time row, 11:00 AM for the second time row and 12:00 PM for the third time row.

But To Time field contains 12:00 AM for all the 3 time rows as shown in the form.

To Time should contain 11:00 AM, 12:00 PM and 01:00 PM

can you show how you generate $modelsTime in the controller that you pass on to the views?
What are values in the database for those fields?

action update –

public function actionUpdate($id)
    {
        $model = $this->findModel($id);

		if(\Yii::$app->user->can('updateAscteacherreport', ['model' => $model]))
		{

		$modelsTime = $model->ascteacherreportdetails;
		$modelsStudentdata = [];
        $oldStudentData = [];

if (!empty($modelsTime)) {
            foreach ($modelsTime as $indexTime => $modelTime) {
                $student = $modelTime->ascteacherreporttimedetails;
                $modelsStudentdata[$indexTime] = $student;
                $oldStudentData = ArrayHelper::merge(ArrayHelper::index($student, 'ASCTeacherReportTimeDetailsId'), $oldStudentData);
            }
        }


    if ($model->load(Yii::$app->request->post())) {

		$modelsStudentdata = [];

		$oldTimeid = ArrayHelper::map($modelsTime, 'ASCReportDetailsId', 'ASCReportDetailsId');
            $modelsTime = Model::createMultiple(Ascteacherreportdetails::classname(), $modelsTime);
            Model::loadMultiple($modelsTime, Yii::$app->request->post());
            $deleteTimeid = array_diff($oldTimeid, array_filter(ArrayHelper::map($modelsTime, 'ASCReportDetailsId', 'ASCReportDetailsId')));
		$valid = $model->validate();
            $valid = Model::validateMultiple($modelsTime) && $valid;



		$studentid = [];

		if (isset($_POST['Ascteacherreporttimedetails'][0][0])) {
                foreach ($_POST['Ascteacherreporttimedetails'] as $indexTime => $student) {
                    $studentid = ArrayHelper::merge($studentid, array_filter(ArrayHelper::getColumn($student, 'ASCTeacherReportTimeDetailsId')));
                    foreach ($student as $indexStudent => $stud) {
                        $data['Ascteacherreporttimedetails'] = $stud;
                        $modelstudent = (isset($stud['ASCTeacherReportTimeDetailsId']) && isset($oldStudentData[$stud['ASCTeacherReportTimeDetailsId']])) ? $oldStudentData[$stud['ASCTeacherReportTimeDetailsId']] : new Ascteacherreporttimedetails;
                        $modelstudent->load($data);
                        $modelsStudentdata[$indexTime][$indexStudent] = $modelstudent;
                        $valid = $modelstudent->validate();
                    }
                }
		}

		$oldstudentid = ArrayHelper::getColumn($oldStudentData, 'ASCTeacherReportTimeDetailsId');
            $deletedstudentid = array_diff($oldstudentid, $studentid);

			if ($valid) {
                $transaction = Yii::$app->db->beginTransaction();
                try {
					if ($flag = $model->save(false)) {

                        if (! empty($deletedstudentid)) {
                            Ascteacherreporttimedetails::deleteAll(['ASCTeacherReportTimeDetailsId' => $deletedstudentid]);
                        }

                        if (! empty($deleteTimeid)) {
                            Ascteacherreportdetails::deleteAll(['ASCReportDetailsId' => $deleteTimeid]);
                        }foreach ($modelsTime as $indexTime => $modelTime) {

                            if ($flag === false) {
                                break;
                            }

                            $modelTime->ASCTeacherReportId = $model->ASCTeacherReportId;


							$modelTime->Time=$modelTime->FromTime.' To '.$modelTime->ToTime;

                            if (!($flag = $modelTime->save(false))) {
                                break;
                            }

                            if (isset($modelsStudentdata[$indexTime]) && is_array($modelsStudentdata[$indexTime])) {
                                foreach ($modelsStudentdata[$indexTime] as $indexStudent => $modelStudentdata) {
                                    $modelStudentdata->ASCReportDetailsId = $modelTime->ASCReportDetailsId;
                                    if (!($flag = $modelStudentdata->save(false))) {
                                        break;
                                    }
                                }
                            }
                        }
					}
					if ($flag) {
                        $transaction->commit();
                        return $this->redirect(['view', '' => $model->ASCTeacherReportId]);
                    } else {
                        $transaction->rollBack();
                    }
                } catch (Exception $e) {
                    $transaction->rollBack();
                }
			}
	}
      return $this->render('update', [
        'model' => $model,
        'modelsTime' => (empty($modelsTime)) ? [new Ascteacherreportdetails] : $modelsTime,
        'modelsStudentdata' => (empty($modelsStudentdata)) ? [[new Ascteacherreporttimedetails]] : $modelsStudentdata,
    ]);
		}
		else
		{
		 throw new ForbiddenHttpException("You can't update other asc report details");
		}
	}

Are the values in database correct?

Can you post relationship getAscteacherreportdetails()?
Since in controller you are not modifying it anywhere, I would suspect either data saved in db are wrong or the relationship is loading wrong data. Can’t think of something else right now

image

Yes the table stores the data correctly from create action.

Why are you storing Time as single field when in form displaying as two fields?
You must be doing conversions somewhere and might well be source of your woes.
Please confirm that you do and post the relevant conversion code.

But I suggest you save them as separate fields

Yes in the table I use only one column called Time and in the model I use two variables From Time and To Time. Then in the create action I join this From Time and To Time with To string like below

$modelTime->Time = $modelTime->FromTime.' To '.$modelTime->ToTime;

How do you load it into the two fields?
Any reason for saving two fields as one?

Below is the action create

public function actionCreate()
    {
		if(\Yii::$app->user->can('creport'))
		{
        $model = new Ascteacherreport;

		$modelsTime = [new Ascteacherreportdetails];
        $modelsStudentdata = [[new Ascteacherreporttimedetails]];

        if ($model->load(Yii::$app->request->post())) {



			$identity = Yii::$app->user->identity->getonlyid();


			$model->UserId = $identity;

			$modelsTime = Model::createMultiple(Ascteacherreportdetails::classname());
            Model::loadMultiple($modelsTime, Yii::$app->request->post());


            $valid = $model->validate();
            $valid = Model::validateMultiple($modelsTime) && $valid;

			if (isset($_POST['Ascteacherreporttimedetails'][0][0])) {
                foreach ($_POST['Ascteacherreporttimedetails'] as $indexTime => $studentdata) {
                    foreach ($studentdata as $indexStudent => $studentdata1) {
                        $data['Ascteacherreporttimedetails'] = $studentdata1;
                        $modelStudentData = new Ascteacherreporttimedetails;
                        $modelStudentData->load($data);
                        $modelsStudentdata[$indexTime][$indexStudent] = $modelStudentData;
                        $valid = $modelStudentData->validate();
                    }
                }
            }
			if ($valid) {
                $transaction = Yii::$app->db->beginTransaction();
                try {
                    if ($flag = $model->save(false)) {
                        foreach ($modelsTime as $indexTime => $modelTime) {

                            if ($flag === false) {
                                break;
                            }

                            $modelTime->ASCTeacherReportId = $model->ASCTeacherReportId;

							$modelTime->Time=$modelTime->FromTime.' To '.$modelTime->ToTime;

                            if (!($flag = $modelTime->save(false))) {
                                break;
                            }

                            if (isset($modelsStudentdata[$indexTime]) && is_array($modelsStudentdata[$indexTime])) {
                                foreach ($modelsStudentdata[$indexTime] as $indexStudent => $modelStudentdata) {
                                    $modelStudentdata->ASCReportDetailsId = $modelTime->ASCReportDetailsId;

                                    if (!($flag = $modelStudentdata->save(false))) {
                                        break;
                                    }
                                }
                            }

                        }
                    }

                    if ($flag) {
                        $transaction->commit();
                        return $this->redirect(['view', 'id' => $model->ASCTeacherReportId]);
                    } else {
                        $transaction->rollBack();
                    }
                } catch (Exception $e) {
                    $transaction->rollBack();
                }
            }
		}

		else
		{




           // return $this->redirect(['view', 'id' => $model->ASCTeacherReportId]);


        return $this->render('create', [
            'model' => $model,

		'modelsTime'=>(empty($modelsTime)) ? [new Ascteacherreportdetails] : $modelsTime,
		'modelsStudentdata'=>(empty($modelsStudentdata)) ? [[new Ascteacherreporttimedetails]] : $modelsStudentdata,
			]);
		}
		}
		else
		{
		   throw new ForbiddenHttpException("You have no access to create report");
		}
    }

It is not necessary to have only single column for time in the table. I can use 2 columns separately for storing From time and To time in the table. But using separate coloumns, will resolve the issue?

My question was how are you loading the one field and split it in the Update action, not the create. Since it saves correctly to the database.

Very likely the source is the conversion error somewhere. Such fields should be loaded in model’s afterFind but even there there can happen some funny things for complex fields. So if you insist on one db field then go that route. But it is not worthy the troubles, IMHO!

I am not splitting it in the update action. When I click on Reset button of the form then correct time is displayed in To Time, but when I click on Add student button to add more student then again the To Time gets changed to 12:00 AM

Since you are storing it as single field, you must load it back to model as different fields at some point. Here is an example that you can use. Seeing all the boiler plate it adds, I would advice against your lame design and split that database table into into individual fields

<?php

use yii\db\ActiveRecord;

class Ascteacherreportdetails extends ActiveRecord
{
    public $FromTime;
    public $ToTime;
    //Time field is loaded by AR from the database

    //two fields must be part of your validation

    public function rules()
    {
        return [
            //your other rules here
            [['FromTime', 'ToTime'], 'string'],
        ];
    }

    public function beforeSave($insert)
    {
        $this->Time = "{$this->FromTime} To {$this->ToTime}";

        return parent::beforeSave($insert);
    }

    public function afterFind()
    {
        $splitTimes = explode('To', $this->Time);
        if (count($splitTimes) == 2) {
            $this->FromTime = trim($splitTimes[0]);
            $this->ToTime = trim($splitTimes[1]);
        }
    }
}
?>


//view form file

<?php foreach ($modelsTime as $indexTime => $modelTime) : ?>

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

    <?= $form->field($modelTime, "[{$indexTime}]ToTime")->label(true)->widget(TimePicker::classname(), [
        'pluginOptions' => [
            'readOnly' => true,
            'minuteStep' => 1,
        ],
    ]) ?>
<?php endforeach; ?>

It is working. To Time is getting correctly fetched.

But the problem is when I click to add new student or new time row and click on reset button, the very first student record gets added on the newly added student form control.

For eg: The report has 3 time details say from 10:00 AM To 11:00 AM, 11:00 AM To 12:00 PM and 12:00 PM To 01:00 PM, and if the user adds one more time row and click on Reset button then the new time row has From Time 10 and To Time 11 and the first student records is added.

Do you think it is worthy wasting your time simply because you refuse to put two fields as two fields in the database? I do not see it worthy!

Moreover, I have written a solution that should work, but you have ignore it!

1 Like

Ok I will use two columns in the database table for FromTime and ToTime.

1 Like

I changed the database structure and added 2 columns for FromTime and ToTime. It is working correctly but as I add new student or new time row and click on Reset button then the first student details get added on the text boxes. Also the two functions beforeSave() and afterFind() functions in the model are not necessary.

These were only necessary for one column design. So Yes, with split columns you do not need them.

Good for you that you resolved it!

But the problem is when I click to add new student or new time row and click on reset button, the very first student record gets added on the newly added student form control.