Search code examples
phpjquerymodel-view-controlleryii2

Problem updating dynamically ActiveForm values with Ajax + RenderPartial/Ajax


Solution

Since the php is not going to be re-executed someone told me to use a return value from the controller as a parameter of the "success" function into ajax's request.
**CONTROLLER**
if(Yii::$app->request->isAjax){
        $query = new \yii\db\Query();
        $rows = $query->select([])
            ->from('task')
            ->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
            ->all();
        $items = "";

        foreach (ArrayHelper::map($rows,'id','name') as $key => $value)
            $items .= '<option value="'. $key .'">'. $value .'</option>\n';
        //<option value="16">test</option> example output
        echo $items;
    }

VIEW

$this->registerJs("$('#task-start').on('change',function(){
            $(\".btn, .btn-success\").prop(\"disabled\",true);
            var date = $(this).val();
            $.ajax({
                url: \"".Yii::$app->request->baseUrl."/task/create\",
                data: {start_date: date},
                type: \"post\",
                success: function(dependency_options){ 
                //dependency_options contains what's returned with 'echo' from the controller  
                $('#task-dependencies').find('option:not(:first)').remove();
                    $('#task-dependencies').append(dependency_options);
                    $(\".btn, .btn-success\").prop(\"disabled\",false);
                },
                error: function () {
                console.log('ERROR')
                $(\".btn, .btn-success\").prop(\"disabled\",false);
                }
            });
    });",View::POS_READY);

Hoping this might help someone.

My problem

I have a DropDownList with no elements and I want to update the list with elements coming from a Query whenever the user changes the datepicker without refreshing the page.

An example of something similar to this would be those forms in which you type your region and it changes the dropdownlist based on what you choose on the field before.

Maybe I'm using a wrong approach to this cause I'm still new to the MVC model and the Yii2 framework so any idea on how to change it's well appreciated.

What I've tried

With this code below as is I had issues cause after the form was created the first time I could not change it later, I've tried to change the html, as you can see from the script in the success ajax function of the View but the script was executed only the first time the view was loaded.

Controller calling the render

public function actionCreate()
    {
        $model = new Task();
        if(Yii::$app->request->isAjax){
            $query = new \yii\db\Query();
            $rows = $query->select([])
                ->from('task')
                ->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
                ->all();
            $items = ArrayHelper::map($rows,'id','name');
            $model->setItems($items);

            return $this->renderPartial('_form',[
                'partial' => true,
                'model' => $model
            ]);
        }
        else if ($this->request->isPost) {
            ..unnecessary code..
        return $this->render('create', [
            'model' => $model,
        ]);
    }

The view "create" basically renders the view _form (autogenerated by Gii)

View _form

<?php
//Here it takes the items in the model
//(which will contain the new items to append after ajax call)
$objects = json_encode($model->getItems() ?? []); 
var_dump($model->getItems() ?? []);

//Here whenever the datepicker is change will fire ajax request
$this->registerJs("$('#task-start').on('change',function(){
        var date = $(this).val();
        var items = ". json_encode($model->getItems() ?? []) .";
        alert(items);
        $.ajax({
            url: \"".Yii::$app->request->baseUrl."/task/create\",
            data: {start_date: date},
            type: \"post\",
            success: function(){
                //$('#task-dependencies').find('option:not(:first)').remove();
                $.each(items, function(key, value) {
                    $('#task-dependencies')
                      .append($('<option>', { value : key })
                      .text(value));
                });
            },
            error: function () {
            console.log('ERROR')
            }
        });
});",View::POS_READY);
?>

<?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'start')->widget(\kartik\date\DatePicker::className(), [
        ...datepicker that triggers onChange...
    ]) ?>

    //The dropdownlist that should dynamically change
    <?= $form->field($model, 'dependencies')->dropDownList(
            $model->getItems(),
            ['prompt' => 'Seleziona una o più dipendenze', 'multiple' => true, 'selected' => 'selected']    // options
        );
    ?>

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

</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css">

Solution

  • You can use Pjax in, yii2:
    Controller:

    public function actionCreate() {
            $model = new Task();
            if( Yii::$app->request->post()  && Yii::$app->request->isPjax ) {
                $query = new \yii\db\Query();
                $rows = $query->select([])
                    ->from('task')
                    ->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
                    ->all();
                $items = ArrayHelper::map($rows,'id','name');
                $model->setItems($items);
    
                // Alert widget renders a message from session flash.
                // Yii::$app->session->setFlash('info', "Ok....");
    
                return $this->renderAjax('_form',[ // Or renderPartial
                    'partial' => true,
                    'model' => $model
                ]);
            }
            // else if ($this->request->isPost) {
                // ..unnecessary code..
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
    

    view

     // submitEvent: The jQuery event that will trigger form handler. Defaults to "submit".
    <?php Pjax::begin(['submitEvent' => 'change']); ?>
    
         <?php $form = ActiveForm::begin([
           'options' => ['data-pjax' => ''],
         ]);
                
                // if(hasFlash ....){ // If used ...
                // Yii::$app->session->getFlash('info')
                // }
         ?>
                <?= $form->field($model, 'start')->widget......
                <?= $form->field($model, 'dependencies')->dropDownList(
                $model->getItems(),
                // ......
    
        <?php ActiveForm::end(); ?>
    
    <?php Pjax::end(); ?>
    

    Tip: The above codes will meet your goal. But you can use the following code to change more and respond to Pjax events.
    jQuery or JavaScript...

    $(document).on('pjax:send', function() {  
      // code ... Example: $('#loading').css({"visibility":"visible"}); //show()
    });
    $(document).on('pjax:complete', function() {
      // code ... // hide()
    });
    
    // Or use if Pjax::$submitEvent = 'submit'
    // $(document).on('change', '#id', function(event) { $(this).submit(); });
    

    If you still have problems, check the date format method in SQL and ....
    Good luck.