Managing your nested dropdown dependency with DepDrop widget

  1. Using DepDrop Widget
  2. Usage Steps

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.