Yii 1.1: Creating a dependent dropdown

94 followers

Often you'll need a form with two dropdowns, and one dropdown's values will be dependent on the value of the other dropdown. Using Yii's built-in AJAX functionality you can create such a dropdown.

The view with the form.

We'll show a form that shows countries and dependent of the country selected will show cities.

echo CHtml::dropDownList('country_id','', array(1=>'USA',2=>'France',3=>'Japan'),
array(
'ajax' => array(
'type'=>'POST', //request type
'url'=>CController::createUrl('currentController/dynamiccities'), //url to call.
//Style: CController::createUrl('currentController/methodToCall')
'update'=>'#city_id', //selector to update
//'data'=>'js:javascript statement' 
//leave out the data key to pass all form values through
))); 
 
//empty since it will be filled by the other dropdown
echo CHtml::dropDownList('city_id','', array());

The first dropdown is filled with several value/name pairs of countries. Whenever it is changed an ajax request will be done to the 'dynamiccities' action of the current controller. The result of that request (output of the 'dynamiccities' action) will be placed in the second dropdown with id is #city_id.

The controller action

It will have to output the html to fill the second dropdownlist. Furthermore it will do that dependent on the the value of the first dropdown.

public function actionDynamiccities()
{
    $data=Location::model()->findAll('parent_id=:parent_id', 
                  array(':parent_id'=>(int) $_POST['country_id']));
 
    $data=CHtml::listData($data,'id','name');
    foreach($data as $value=>$name)
    {
        echo CHtml::tag('option',
                   array('value'=>$value),CHtml::encode($name),true);
    }
}

It will retrieve all cities that have as a parent the id of the first dropdown. It will then output all those cities using the tag and the output will end up in the second dropdown.

You might wonder where the $_POST['country_id'] comes from. It's simple, when the 'data' key of the ajax array in the first dropdown is empty, all values of the elements of the form the dropdown is in, will be passed to the controller via the ajax request. If you're using Firebug you can inspect this request and see for yourself.

This behaviour can also be changed. By default the value of the 'data' key in the ajax configuration array is js:jQuery(this).parents("form").serialize(). The preceding js: indicates to Yii that a javascript statement will follow and thus should not be escaped. So, if you change the 'data' key to something else preceded by 'js:' you can fill in your own statement. The same applies to the 'success' parameter.

For this to work you also need to edit the Method accessRules() (if available) in your current Controller. In this example we would change

array('allow', // allow authenticated user to perform 'create' and 'update' actions
                'actions'=>array('create','update'),
                'users'=>array('@'),
            ),

to

array('allow', // allow authenticated user to perform 'create' and 'update' actions
                'actions'=>array('create','update','dynamiccities'),
                'users'=>array('@'),
            ),

in order to allow access for authenticated Users to our Method 'dynamiccities'. To allow access to eg any User or to use more complex rules please read more about accessRules and Authentication here.

Note: For testing purposes you could also comment out

'accessControl', // perform access control for CRUD operations

in the filters() Method. This will disable all access controls for the controller. You should always re-enable it after testing.

Related Links

Persian version

Total 20 comments

#18237 report it
Anjith at 2014/10/03 10:58pm
WHen I change the option in dropdown the ajax call is going to site/index not to controller i defined

When I change the option in dropdown the ajax call is going to site/index not to controller i defined. Below is the code of view and controller, I am using default siteController.php, it doesn't have any access code anywhere, I dont see any access array in the controller. View code

<div class="row">
        <?php echo $form->labelEx($model,'city'); ?>
        <?php echo $form->dropDownList($model,'city', CHtml::listData(Connecttbl::model()->findAll(array('order' => 'city ASC')), 'id', 'city'), array('empty'=>'Select city'), array(
            'ajax' => array(
                'type' => 'POST',
                'url' => CController::createUrl('getstreets'),
                'data' => array('category', 'js:this.value'),
                'update' => '#'.CHtml::activeId($model, 'streets')
        ))); ?>
        <?php echo $form->error($model, 'city'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'streets'); ?>
        <?php echo CHtml::dropDownList('streets','', array()); ?>
        <?php echo $form->error($model,'streets'); ?>
    </div>

Controller

public function actiongetstreets()
    {
        $data = Connecttbl::model()->findAll('id=:id', array(':parentid'=>(int)$_POST['testtbl[city]']));
        $data = CHtml::listData($data, 'id', 'street');
        foreach($data AS $value=>$name)
        {
            echo CHtml::tag('option',
                   array('value'=>$value), CHtml::encode($name),true);
        }
    }
#17917 report it
Amazing at 2014/08/11 09:11am
Response Time @pjravs

@pjravs without seeing what you are doing even if the code is similar, it would be hard to pinpoint what the exact issue is. It could be your queries are not optimized or there are no indexes on the searched column in the database (dealing with large datasets) or something else.

Kindly share your code and lets see what's going on.

Thanks

#17913 report it
pjravs at 2014/08/10 08:38pm
Response Time

Good Day!

@Amazing Thanks for the feedback. I have used firebug and noticed that the request upon the server side takes about 3 to 4 seconds for the dropdown to be filled up with options. The code was identical to the example above.

#17910 report it
Amazing at 2014/08/10 10:30am
Slow response @pjravs

@pjravs, please could you explain further the exact problem with codes as well so you can get someone to help.

What I am not getting is whether it takes a long time for the second dropdown to receive data after selection in the first dropdown or It takes having to make several selections in the first dropdown for it to work.

Thanks

#17909 report it
pjravs at 2014/08/10 04:42am
Slow Response

Good Day!

I have problem in using the dependent dropdown specially on its response time. It takes selecting many options in the dropdown for it to respond. Thanks for any feedback.

God Bless!

#17567 report it
vijay p s at 2014/07/03 02:53am
@ Error FIX

Atlast I cleared my error.

The problem is in the access rule. when we add a new function in the controller we need to add the function name in the access rule and we need to specify the authenticate for user or for the admin.. that's y i got error in POST the value to the controller.

My new function in my controller is dynamicCategory().

array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'actions'=>array('admin','delete','DynamicCategory'),
                'users'=>array('admin'),
            ),

PLZ read the article carefully line by line before u start working. @amazing thank you for your endless support and for the yiiteam.

#17565 report it
vijay p s at 2014/07/03 02:08am
About Error

@amazing

the problem is in the POST. Empty value is send to the controller.

In the browser console it shows like this when i execute the ddl.

POST http://192.168.1.13/fdms/index.php?r=Subheads/dynamicCategory 403 (Forbidden)

How can i resolve this????

&thank you for ur support amazing.

#17554 report it
Amazing at 2014/07/02 01:47am
@Vijay Error Fix

@Vijay, Did our suggestions worked? Any feedback...

#17529 report it
Amazing at 2014/06/28 02:00pm
Error Fix @Vijay

Hello @Vijay,

Thank you for your feedback,

From your code...even if you use queryAll, it might not work as expected because CHtml::listData() accepts models but queryAll() will return associative arrays.

Besides queryAll() is a method stemming from Yii::app()->db instance and hence Yii::app()->db->createCommand($sql)->queryAll();

$command = Yii::app()->db->createCommand($sql)->queryAll(true,
  array('state', $_POST['Address']['state']));

Hence try the code below and let me know the outcome assuming you have a city model class.

public function actionDynamiccities()
{
$stateCode = isset($_POST['Address']['state']) ? $_POST['Address']['state'] : '';
 
        $criteria = new CDbCriteria();
        $criteria->select = array('city_id','city');
        $criteria->condition = 'state_id=:state';
        $criteria->params = array(':state'=> $stateCode);
        // $criteria->order = 'city ASC'; uncomment to order the list
 
        $cities = City::model()->findAll($criteria);
 
        if (is_array($cities) )
        {
            foreach($cities as $value=>$name)
                {
                    echo CHtml::tag('option',
                               array('value'=>''.$value),CHtml::encode($name),true);
                }
        } 
        else
         {
            echo CHtml::tag('option',
                       array('value'=>''.'0'),CHtml::encode('No cities found'),true);
         }
}
#17528 report it
puritania at 2014/06/28 07:01am
Try

@Vijay P S call queryAll instead execute

try:

$sql = "SELECT * FROM city WHERE state_id = :state";
$rows = $command = Yii::app()->createCommand($sql)->queryAll(true,
  array('state', $_POST['Address']['state']));
 
foreach($rows as $row) {
  echo CHtml::tag('option', array('value'=>$row['city_id'], 'encode' => true),
    $row['city']);
}
#17527 report it
vijay p s at 2014/06/28 05:12am
Code

@amazing.. this s my code on controller i got that "Invalid argument supplied for foreach().."

public function actionDynamiccities()
 
{
  $sql = "SELECT * FROM city ".
         "WHERE state_id = :state";
  $command = Yii::app()->createCommand($sql);
  $command->bindValue(':state', $_POST['Address']['state']);
  $data = $command->execute();
  /* $data=City::model()->findAll('state_id=:state_id', 
                  array(':state_id'=>(int) $_POST ['Address']['state']));*/
 
    $data=CHtml::listData($data,'city_id','city');
 
 foreach($data as $value=>$name)
    {
        echo CHtml::tag('option',
                   array('value'=>$value),CHtml::encode($name),true);
    }
}

I got error error in this line. if i comment this line in my _view. i didnt get error and i didnt got result..

echo $form->dropDownList($model,'city','',array());
#17526 report it
Amazing at 2014/06/28 03:16am
Error - Reply

Vijay P S At 2014/06/28 02: 13am error I got this error help me out

Invalid argument supplied for foreach()..

@Vijay, foreach() accepts an array to begin with so if your argument is not an array, it throws that warning. Alternatively check if the arg passed is array before calling foreach

if (is_array($arrayToSend) ){
 
    foreach ($arrayToSend as $value ){
        ..
    }
}

That been said, it would be helpful if you post your code with your questions so you can get adequate help from members.

Thank you.

#17525 report it
vijay p s at 2014/06/28 02:13am
error

I got this error help me out

Invalid argument supplied for foreach()..

#17081 report it
Todd Anstis at 2014/04/30 03:07pm
Multiple Ajax calls

For some reason this code is resulting in multiple ajax requests (I can see 6 of them in Firebug when I make a selection from my dropdown list). What am I doing wrong here?

View:

<?php echo $form->dropDownListRow($plan,'plan_id',
            CHtml::listData($this->plans, 'id', 'name'),
            array(
                'empty'=>'--Select a Plan--', '',
                'ajax' => array(
                    'type'=>'POST',
                    'url'=>$planDetailUrl,
                    'data'=> "js:$('#plan-form').serialize()",
                    'dataType'=>'json',
                    'beforeSend'=>"function() {
                        $('div#planForm').addClass('loading');
                    }",
                    'success'=>"function(data) {
                        if (data.status == 'success') {
                            if(data.feeAmount != null) {
setFeeAmount(data.feeAmount);
                        }
if(data.billInterval=='Monthly') {
                                    $('label[for=\"pymt_day_of_month\"]').css('display','');
                                    $('#pymt_day_of_month').css('display','');
                                }
                        $('div#planForm').removeClass('loading');
                    }",
                ))); ?>

Controller:

public function actionPlandetails() {
        $id = $_POST['UserPlan']['plan_id'];
 
        $plan = Plan::model()->findByPk($id);
 
        $fee = $plan == null ? 0.00 : $plan->fee;
        $billInterval = $plan->billInterval == null ? '' : $plan->billInterval->name;
 
        if(Yii::app()->request->getIsAjaxRequest()) {
            echo CJSON::encode(array(
                    'status' => 'success',
                    'feeAmount' => !strrchr($fee,'.') ? $fee.'.00' : $fee,
                    'billInterval' => $billInterval,
                )
            );
 
            Yii::app()->end();
        }
    }
#17075 report it
Gerhard Liebenberg at 2014/04/30 09:19am
Using CActiveForm with a Model

Thanx Mahdi Miad, I got that working.

Just a change I had to make to the controller code:

array(':parent_id'=>(int) $_POST['currentController'] ['country_id']));));

must be

array(':parent_id'=>(int) $_POST['currentModel'] ['country_id']));

Regards

#16927 report it
Mahdi Miad at 2014/04/14 05:38am
Using CActiveForm with a Model

To use this article using model data here are the changes: at the View

echo $form->dropDownList($model, 'country_id' ,array(1=>'USA',2=>'France',3=>'Japan'),
array(
'ajax' => array(
'type'=>'POST', //request type
'url'=>CController::createUrl('currentController/dynamiccities'), //url to call 
'update'=>'#'.CHtml::activeId($model,'city_id'), 
))); 
 
echo $form->dropDownList(($model, 'city_id',  array());

And at the Controller

public function actionDynamiccities()
{
    $data=Location::model()->findAll('parent_id=:parent_id', 
           array(':parent_id'=>(int) $_POST['currentController'] ['country_id']));));
 
    $data=CHtml::listData($data,'id','name');
    foreach($data as $value=>$name)
    {
        echo CHtml::tag('option',
                   array('value'=>$value),CHtml::encode($name),true);
    }
}

As you can see the only change at the controller is $_POST['currentController'] ['country_id'] instead of $_POST['country_id']

#16319 report it
kristantoarman at 2014/02/10 04:15am
set 1 textfield from 2 dropdownlist selected value

could you help me for this:

settxt

this form :

<tr>
        <td><?php echo $form->labelEx($model,'kd_jenisdokumen'); ?></td>
                <td><?php echo $form->dropDownList($model,'kd_jenisdokumen',CHtml::listData(MsJenisdokumen::model()->findAll(),
                    'kd_jenisdokumen','deskripsi'),
                    array(
                        'prompt'=>'Pilih Jenis',
                        'ajax'=>array(
                            'empty'=>'Pilih Jenis',
                            'type'=>'POST',
                            'url' => CController::createUrl('jenis'),
                            'data'=> array('jdok'=>'js:this.value'),
                            'update'=>'#jendok',
                            ))
                    );
                ?>
                </td>
        <td><?php echo $form->error($model,'kd_jenisdokumen'); ?></td>
            </tr>
            <tr>
        <td><?php echo $form->labelEx($model,'kd_departemen'); ?></td>
                <td><?php echo $form->dropDownList($model,'kd_departemen',CHtml::listData(MsDepartemen::model()->findAll(),
                    'kd_departemen','deskripsi'),
                    array(
                        'empty'=>'Pilih Departemen',
                        'ajax'=>array(
                            'type'=>'POST',
                            'url' => CController::createUrl('dept'),
                            'data'=> array('dept'=>'js:this.value'),
                            'update'=>"#dept",
                            ))
                    );
                    ?>
                </td>
                <td><?php echo $form->error($model,'kd_departemen'); 
                ?></td>
            </tr>
            <tr>
                <td>
                </td>
                <td id="jendok"></td>
                <td></td>
            </tr
            <tr>
                <td></td>
                <td id="dept"></td>
                <td></td>
            </tr>
            <tr>
        <td><?php echo $form->labelEx($model,'no_dokumen'); ?></td>
                <td><?php echo $form->textField($model,'no_dokumen',array('id'=>'no_dokumen','size'=>20,'maxlength'=>20,));
                          echo CHtml::Button('check',array('onclick'=>"{check();}"));
                    ?>
</td>
        <td><?php echo $form->error($model,'no_dokumen',array('id'=>'no_dokumen')); ?></td>

this controller :

// function untuk mengambil POST jenis dokumen
        public function actionJenis(){
            $data = $_POST['jdok'];
            echo CHtml::textField ('jendok', 'BUT-'.$data.'/');
        }
 
        // function untuk mengambil POST departemen
        public function actionDept(){
            $data2 = $_POST['dept'];
            echo CHtml::textField ('dept', $data2.'-');
        }

I want to set just 1 textfield in no dokumen. Please help thx...

#16139 report it
sercio at 2014/01/23 01:05am
Thanks, just one question

Hi, thank you this is a great tutorial.

Just a quick question. In the controller, why it is like this:

public function actionDynamiccities()
{
    $data=Location::model()->findAll('parent_id=:parent_id', 
                  array(':parent_id'=>(int) $_POST['country_id']));
}

and not like this:

public function actionDynamiccities()
{
    $data=Location::model()->findAll('parent_id=' . $_POST['country_id']);
}

Second one also seems to work in my system.

#16089 report it
Patrice at 2014/01/18 02:25pm
Thanks

I successfully followed your method. Thanks to Firebug, as you said, I saw that in the action, the values are named :

$_POST['Mymodel']['myfield']

Thanks again. Nice explanations.

Leave a comment

Please to leave your comment.

Write new article