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
andcomplete
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])->asArray()->all(); $pages = Content::find()->select(['id', 'title'])->where(['lab' => $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).