Chained, AJAX-updated listboxes in Yii2

There’s a great answer at Stack Overflow that shows, how in Yii2 you can implement a form containing listbox and two input fields. Listbox contains data from one model and when user selects any option, remaining two input fields should be populated with relevant values from related model.

I wrote an extended version of this code. It populates two other listboxes instead of just input fields. And it uses a little bit less deprecated code! :>

Changes, I implemented in my solution:

  • populate two listboxes instead of two input fields,
  • block usage of listboxes during update, using disabled attribute,
  • populate fields initially, when page loads, not only after user selection,
  • drop usage of deprecated success and complete in favor of .always() and .done().

First, let’s deal with data source, that is — an action, that will be called by our AJAX. Put this to your controller:

public function actionLabData($id)
{
    $devices = Device::find()->select(['id', 'name'])->where(['lab_id' => $id])->asArray()->all();
    $pages = Content::find()->select(['id', 'title'])->where(['lab_id' => $id])->asArray()->all();

    Yii::$app->response->format = yiiwebResponse::FORMAT_JSON;

    return [
        'devices' => $devices,
        'pages' => $pages
    ];
}

It finds all models, that matches given $id and encodes them as fully-qualified JSON response.

Then, we need a piece of Javascript code, that fires AJAX and chops the results. Put this to your view:

< ?php
    $this->registerJs("
        function refreshListBoxes()
        {
            $('#file-page').attr('disabled', true);
            $('#file-device').attr('disabled', true);

            $.ajax({
                url: '".Url::toRoute("file/lab-data")."',
                dataType: 'json',
                method: 'GET',
                data: {id: $('#file-lab').val()}
            }).always(function () {
                $('#file-page').empty().attr('disabled', false);
                $('#file-device').empty().attr('disabled', false);
            }).done(function (data) {
                $(data.pages).each(function() {
                    $('<option></option>').val(this.id).text(this.title).appendTo('#file-page');
                });

                $(data.devices).each(function() {
                    $('<option></option>').val(this.id).text(this.name).appendTo('#file-device');
                });
            });
        }

        refreshListBoxes();

        $('#file-lab').on('change', function() {refreshListBoxes()});
    ", yiiwebView::POS_READY, 'refresh-list-boxes');
?>

This code assumes, that you want to take pages item from JSON result array and populate $(‘#file-page’) listbox with data, it contains. Then repeat the same for devices item and $('#file-device') listbox.

Above code first disables each listbox being updated, then it fires AJAX, gets results, empty and re-populate each listbox and finally enables them back. Listboxes will be enabled always, no matter, if there is a success or an error. In second case, listbox will be enabled, but empty (no real error handling implemented here).

Leave a Reply