Yii 2.0: Managing your nested dropdown dependency with DepDrop widget

42 followers

If you are coming over to Yii 2 from Yii 1.x, you may have already read this useful wiki for creating dependent dropdowns. You can use a similar approach in Yii 2 to do the same. But if you are looking at a prebuilt solution that helps you manage it easier, read along.

Using DepDrop Widget

This widget is part of the yii2-widgets package and specifically handles dependent dropdowns. You can refer documentation and usage demos for this widget for details. The explanation of the examples is mentioned here for reference.

Tip: You can use the widget for rendering dependent dropdown lists using yii\helpers\Html::dropDownList AND/OR kartik\widgets\Select2 widget.

Usage Steps

Example

  1. Let's assume you have 3 dropdown fields cat, subcat, and prod.
  2. subcat depends on cat
  3. prod depends on both cat and subcat
  4. Let's assume you have these following initial preselected values for the dropdown fields
$model->cat = 2;
$model->subcat = 2;
$model->prod = '2.2';

View

  1. You want to initialize the fields with preselected data.
  2. You must set the data property for the dependent fields that must contain the preselected value as a key.
  3. Set the depends property for subcat and prod as seen below
  4. Set the url property to an action that will be called to render the list
  5. Set the initialize property to true for the last child in your nested list (i.e. prod in this case). This will enable you to tell the plugin to automatically fire the ajax calls on document load and generate the dropdowns data in the right order sequentially for the nested chain.
  6. Alternatively, you can also set the data property of each dropdown and not set the initialize property, so that no ajax calls are fired on document load.
use kartik\widgets\DepDrop;
use yii\helpers\Html;
 
// Parent 
echo $form->field($model, 'cat')->dropDownList($catList); 
    // $catList is the initial list of values for `cat`
 
// Child # 1
echo $form->field($model, 'subcat')->widget(DepDrop::classname(), [
    'data' => [2 => 'Music'], // ensure at least the preselected value is available
    'pluginOptions'=>[
        'depends'=>[Html::getInputId($model, 'cat')], // the id for cat attribute
        'placeholder'=>'Select...',
        'url'=>Url::to(['/site/subcat'])
    ]
]);
 
// Child # 2
echo $form->field($model, 'prod')->widget(DepDrop::classname(), [
    'data' => ['2.2' => 'Product Music 2'], // ensure at least the preselected value is available
    'pluginOptions'=>[
        'depends'=>[
            Html::getInputId($model, 'cat'), // the id for cat attribute
            Html::getInputId($model, 'subcat'), // the id for subcat attribute
        ],
        'placeholder'=>'Select...',
        'url'=>Url::to(['/site/prod']),
        'initialize'=>true
    ]
]);

Controller

  1. The DepDrop widget uses the dependent-dropdown plugin. This plugin triggers an ajax response on a parent dropdown change, and passes in an array variable depdrop_parents for each dependent field.
  2. Your controller action must read this and return a json encoded subcat or prod list based on dependencies as seen below.

Note: its important to adhere to the return format of the json encoded response as shown in the examples and in the widget documentation. This differs a bit if you are returning a list with optgroups.

// Generate list of subcat based on cat
public function actionSubcat() {
    $out = [];
    if (isset($_POST['depdrop_parents'])) {
        $parents = $_POST['depdrop_parents'];
        if ($parents != null) {
            $cat_id = $parents[0];
            $out = self::getSubCatList($cat_id); 
            // the getSubCatList function will query the database based on the
            // cat_id and return an array like below:
            // [
            //    ['id'=>'<sub-cat-id-1>', 'name'=>'<sub-cat-name1>'],
            //    ['id'=>'<sub-cat_id_2>', 'name'=>'<sub-cat-name2>']
            // ]
            echo Json::encode(['output'=>$out, 'selected'=>'']);
            return;
        }
    }
    echo Json::encode(['output'=>'', 'selected'=>'']);
}
 
// Generate list of products based on cat and subcat
public function actionProd() {
    $out = [];
    if (isset($_POST['depdrop_parents'])) {
        $ids = $_POST['depdrop_parents'];
        $cat_id = empty($ids[0]) ? null : $ids[0];
        $subcat_id = empty($ids[1]) ? null : $ids[1];
        if ($cat_id != null) {
           $data = self::getProdList($cat_id, $subcat_id);
            /**
             * the getProdList function will query the database based on the
             * cat_id and sub_cat_id and return an array like below:
             *  [
             *      'out'=>[
             *          ['id'=>'<prod-id-1>', 'name'=>'<prod-name1>'],
             *          ['id'=>'<prod_id_2>', 'name'=>'<prod-name2>']
             *       ],
             *       'selected'=>'<prod-id-1>'
             *  ]
             */
 
           echo Json::encode(['output'=>$data['out'], 'selected'=>$data['selected']]);
           return;
        }
    }
    echo Json::encode(['output'=>'', 'selected'=>'']);
}

And that's pretty much it. You should see the dependent dropdowns working now automatically, as seen in the demo.

Total 6 comments

#18882 report it
Thani at 2015/01/28 09:57pm
Not able to see the response data in depdropdown.

Hi Kartik,

I am getting the below error when i use DepDrop Can you please help me.

Error:

I am getting the following response, but not loading the doctor dropdown based on the doctor type.

{"output":[{"id":"79","first_name":"doctor abc"}],"selected":""}

but it show the blank.

_form.php

<?php 
        $dataList1=ArrayHelper::map(\app\models\sysconfig\CodeDoctorTypeRecord::find()->asArray()->all(), 'id', 'code_doctor_type_desc');
 
        echo $form->field($model, 'code_doctor_type_idn')->dropDownList($dataList1)
    ?>
 
 
    <?=
 
      $form->field($model, 'doctor_idn')->widget(DepDrop::classname(), [
    'options'=>['id'=>'subcat-id', 'class'=>'input-large form-control'],
    'pluginOptions'=>[
                //'depends'=>['cat-id'],
                'depends'=>[Html::getInputId($model, 'code_doctor_type_idn')], // the id for cat attribute
                'placeholder'=>'Select...',
                'url'=>Url::to(['subcat'])
            ]
        ]);
 
    ?>

DoctorRecord.php

public static function getOptionsbyType($doctor_type_id) {
$data = DoctorRecord::find()->where(['code_doctor_type_idn'=>$doctor_type_id])->select(['id','first_name'])->asArray()->all();
$value = (count($data) == 0) ? ['' => ''] : $data;
 
return $value;
}

OrderControler.php

public function actionSubcat() {
$out = [];
if (isset($_POST['depdrop_parents'])) {
$parents = $_POST['depdrop_parents'];
if ($parents != null) {
$cat_id = $parents[0];
$out = \app\models\doctor\DoctorRecord::getOptionsbyType($cat_id);
echo Json::encode(['output' => $out, 'selected' => '']);
return;
}
}
echo Json::encode(['output' => '', 'selected' => '']);
}

Thanks, Thani

#18484 report it
kashan at 2014/11/05 03:16am
on update or after server validation return(some validation failed), load depended child with preselected value

I have change the following line 'data' => [2 => 'Music'] to

'data' =>($model->cat_id)?ArrayHelper::map(subcat::find()->where('cat_id=:cat_id',[':cat_id'=>$model->cat_id])->asArray()->all(), 'subcat_id', 'subcat_name'):[],

#18100 report it
Kartik V at 2014/09/08 05:41am
Re: Loading of initial value

@Pierre the value of your field (selected value) for an UPDATE scenario, should be set or derived from your server code. For example, if your attribute is named subcat and you are using with a model, then the value of $model->subcat will determine your selected value. If you are not using with a model, then you must pass the value property to the widget.

#18099 report it
Pierre M at 2014/09/08 05:28am
Loading of initial value

Hello, I use the widget in a CRUD form and I got everything working fine, but when I return in the form to edit the model, the selected value does not seem to be selected, even if I call with initialize=true. The option values are all loaded fine, but the selected value is not selected. Am I missing something? Do I have to do that myself with some javascript? Thanks for all your wonderful widgets. - P.

#17385 report it
Kartik V at 2014/06/01 01:44pm
Updating other fields

@zvik2004 you need to write your own javascript method for the onchange event for the last dropdown that will trigger an ajax call to fetch description and set the description field value.

#17384 report it
zvik2004 at 2014/06/01 10:38am
Widget updating based on the last dropdown

Hi Kartik
As always, thank you very much for all your work, always looking forward for your next widget :-)
I would like to have a code example, if you can, for the following:
Let's say I have 2 dropdown lists, and I select in the first one a city, and in the next one, I select a bank name, e.g. citybank (one of many that exist in that city), I would like to have below these 2 dropdown lists a description of that bank, e.g. how many branches it has in this city.
Of course, this description should be updated when the last dropdown list is updated, and should be empty when nothing is selected in the last dropdown list.
Thanks! Zvi.

Leave a comment

Please to leave your comment.

Write new article