Yii Ajax Update Depending Dropdown Selection

I have dropdown list where there are five or six items (here items are services). I used here ajax request which updates the id “package_id” from the URL ‘url’=>$this->createUrl(‘servicePackage’), once I select any service item. Till now, I have done successfully. But I want that ajax should use different URL and update different id i.e. “registrationid” if I select 4th number item, otherwise it should update “package_id”. Is it possible?

Is it possible?

The form is:




<div class="form-group">

    <?php echo $form->labelEx($model,'service_id', array('class' => 'control-label col-lg-4')); ?>

    <div class="col-lg-8">

    <?php echo $form->dropDownList($model,'service_id',CHtml::listData(Service::model()->findAll(),'id', 'name'),

    array(

        'class' => 'form-control',

        'prompt'=>'Select a Service',

        'ajax' => array('type'=>'POST',

            'url'=>$this->createUrl('servicePackage'), 

            'update'=>'#'.CHtml::activeId($model, 'package_id'), // ajax updates package_id, but I want ajax update registration_id if I select item no 4

            'data'=>array('service_id'=>'js:this.value'),

        )));

    ?>

    </div>

    <?php echo $form->error($model,'service_id'); ?>

</div>


<div class="form-group" id="packageid">

    <?php echo $form->labelEx($model,'package_id', array('class' => 'control-label col-lg-4')); ?>

    <div class="col-lg-8">

    <?php

    echo $form->dropDownList($model,'package_id',$model->getServicePackage($model->service_id),array(

        'class' => 'form-control','prompt'=>'Select a Package',

        'ajax' =>

        array('type'=>'POST',

            'url'=>$this->createUrl('packagePrice'), //url to call.

            'update'=>'#price', //selector to update

            'data'=>array('package_id'=>'js:this.value'),

        )));

    ?>

    </div>

    <?php echo $form->error($model,'package_id'); ?>

</div>


<div class="form-group" id="registrationid">

    <?php echo $form->labelEx($model,'registration_id', array('class' => 'control-label col-lg-4')); ?>

    <div class="col-lg-8">

        <?php

        $registrants = Registration::model()->findAll();

        $data = CHtml::listData($registrants,'id', function($registrants) {

            return CHtml::encode($registrants->name.' ('.$registrants->id.')');

        });

        echo $form->dropDownList($model,'registration_id',$data,

            array(

                'class' => 'required form-control chzn-select',

                'prompt'=>'Select a Registrant',

                'ajax' => array('type'=>'POST',

                    'url'=>$this->createUrl('Order'), //url to call.

                    'update'=>'#'.CHtml::activeId($model, 'order_id'), //selector to update

                    'data'=>array('registration_id'=>'js:this.value'),

                )


            ));

        ?>

    </div>

    <?php echo $form->error($model,'registration_id'); ?>

</div>






You may use ajax ‘success’ setting instead of yii-provided ‘upate’ and put more complex logic there.

Example:


'success' => "function(data){

// you logic is here

}",



Documentation

Yes I understand that I did but it would easier to apply condition in ajax URL and update. However, thanks to reply.

Do you mean you want something like the following:




// pseudocode, shows the common logic

if ($model->isRegistrationUpdate) {

    $ajaxUrl = $this->createUrl('serviceRegistration');

    $ajaxUpdate = CHtml::activeId($model, 'registration_id');

} else {

    $ajaxUrl = $this->createUrl('servicePackage');

    $ajaxUpdate = CHtml::activeId($model, 'package_id');

}

// ajax settings

array(

    'type'   => 'POST',

    'url'    => $ajaxUrl,

    'update' => '#' . $ajaxUpdate,

    'data'   => array('service_id' => 'js:this.value'),

)


// in a model

public function getIsRegistrationUpdate()

{

    return $this->id == 4;

}



?

Thanks you are trying to cooperatw me. Actually, I need as: I have dropdown list which have six items and it is dependent dropdown that selecting one item updates other dropdown list. There are few complexity has been risen that is 4th number of item should update different things, so I should use different method in controller. So there will be different URL and Update id for that item. Please see in following code:




<div class="form-group">

    <?php echo $form->labelEx($model,'service_id', array('class' => 'control-label col-lg-4')); ?>

    <div class="col-lg-8">

    <?php echo $form->dropDownList($model,'service_id',CHtml::listData(Service::model()->findAll(),'id', 'name'),

    array(

        'class' => 'form-control',

        'prompt'=>'Select a Service',

        'ajax' => array('type'=>'POST',

            'url'=>$this->createUrl('servicePackage'), // here for a specific item, there should be different URL

            'update'=>'#'.CHtml::activeId($model, 'package_id'), // here for a specific item, there should be different update

            'data'=>array('service_id'=>'js:this.value'),

        )));

    ?>

    </div>

    <?php echo $form->error($model,'service_id'); ?>

</div>




I can do this other way but I feel that if I could use any condition in the URL and Update , the work would so flexible. If you have idea, I would be grateful.

Many thanks

So, seems my previous example satisfies your needs fully. Or what is not suitable for you in this approach? Pls notice that’s a common idea, which you may use according to your actual needs and code design.

Sorry to late response. I tried this approach but do not understand how to make this code conditional within onchange of dropdownlist.

the part of the form’s code where I should use the condition onChange of select item.




<?php echo $form->dropDownList($model,'service_id',CHtml::listData(Service::model()->findAll(),'id', 'name'),

    array(

        'class' => 'form-control',

        'prompt'=>'Select a Service',

        'ajax' => array('type'=>'POST',

            'url'=>$this->createUrl('servicePackage'), // here for a specific item, there should be different URL

            'update'=>'#'.CHtml::activeId($model, 'package_id'), // here for a specific item, there should be different update

            'data'=>array('service_id'=>'js:this.value'),

        )));

    ?>




Thanks

Yes, sorry, my bad missing an important point.

This time I’ve tested all the processing so it should work fine now (but sorry, I’ll post solution with my own names of variables/urls).

Probably there’re even better approaches, but that’s what I’ve got at this moment (and there’s a usual lack of time :) ).

So, I came up with such possible solutions:

  1. call relevant logic (method) depends on an item at a called controller method, handle response in ‘success’ option, not using ‘update’

  2. write custom handling of onclick event (you may also use ‘onclick’ option instead of ‘ajax’ in dropDownList options)

  3. Probably the one you were looking for:

Create MyHtml.php file and put it to /components, extend it from CHtml class and put activeDropDownList(), clientChange() and ajax() into it, then change this line in ajax() method:





//            $options['url'] = self::normalizeUrl($options['url']);

            $options['url'] = new CJavaScriptExpression('(jQuery(this).val() == 4 ? "' . self::normalizeUrl($options['data-url'])

                            . '" : "' . self::normalizeUrl($options['url']) . '")');



Call dropDown this way:





$model = new Users();

echo MyHtml::activedropDownList($model, 'name', CHtml::listData(Users::model()->findAll(), 'id', 'name'), array(

    'class' => 'form-control',

    'prompt' => 'Select a Service',

    'ajax' => array(

        'data-url'=>$this->createUrl('serviceRegistration'),

        'url' => $this->createUrl('servicePackage'), // it is selected at MyHtml::ajax() which URL to use

        'type' => 'POST',

        'dataType' => 'json',

        'success' => 'function(data){

            if(data.registration){

             	console.log("answer from registration");

            }else if(data.package){

             	console.log("answer from package");

            }

        }',

        'data' => array('id' => 'js:this.value'),

    )

    ));



in a controller you’ll basically have:





    public function actionServiceRegistration()

    {

        // logic

        echo json_encode(array("registration"=>'yes, that was registration handler'));

    }

    public function actionServicePackage()

    {

        // logic

        echo json_encode(array("package"=>'yes, that was package handler'));

    }



Actually, I’m sure there’s a number of similar approaches. For example, you could pass needed url in an option and to send a request to that url – but probably you’ll need overwrite CHtml again, I’m not 100% sure in this point.

Hope it’ll help. Let me know your thoughts.

Thanks yugene for spending a lot of time to cooperate me. I understand this logic but I am new in this approach. I am getting the following error but do not understand how to fix. Please could you tell me why this error comes:

Fatal error: Call to undefined method YiiBase::normalizeUrl() in C:\xampp\htdocs\ssw\protected\components\MyHtml.php on line 2

Thanks again

Your MyHtml class should look like the following:




class MyHtml extends CHtml

{


    public static function activeDropDownList($model, $attribute, $data, $htmlOptions = array())

    {

        self::resolveNameID($model, $attribute, $htmlOptions);

        $selection = self::resolveValue($model, $attribute);

        $options = "\n" . self::listOptions($selection, $data, $htmlOptions);

        self::clientChange('change', $htmlOptions);


        if ($model->hasErrors($attribute))

            self::addErrorCss($htmlOptions);


        $hidden = '';

        if (!empty($htmlOptions['multiple'])) {

            if (substr($htmlOptions['name'], -2) !== '[]')

                $htmlOptions['name'].='[]';


            if (isset($htmlOptions['unselectValue'])) {

                $hiddenOptions = isset($htmlOptions['id']) ? array('id' => self::ID_PREFIX . $htmlOptions['id'])

                            : array('id' => false);

                $hidden = self::hiddenField(substr($htmlOptions['name'], 0, -2), $htmlOptions['unselectValue'], $hiddenOptions);

                unset($htmlOptions['unselectValue']);

            }

        }

        return $hidden . self::tag('select', $htmlOptions, $options);

    }


    protected static function clientChange($event, &$htmlOptions)

    {

        if (!isset($htmlOptions['submit']) && !isset($htmlOptions['confirm']) && !isset($htmlOptions['ajax']))

            return;


        if (isset($htmlOptions['live'])) {

            $live = $htmlOptions['live'];

            unset($htmlOptions['live']);

        }

        else

            $live = self::$liveEvents;


        if (isset($htmlOptions['return']) && $htmlOptions['return'])

            $return = 'return true';

        else

            $return = 'return false';


        if (isset($htmlOptions['on' . $event])) {

            $handler = trim($htmlOptions['on' . $event], ';') . ';';

            unset($htmlOptions['on' . $event]);

        }

        else

            $handler = '';


        if (isset($htmlOptions['id']))

            $id = $htmlOptions['id'];

        else

            $id = $htmlOptions['id'] = isset($htmlOptions['name']) ? $htmlOptions['name']

                        : self::ID_PREFIX . self::$count++;


        $cs = Yii::app()->getClientScript();

        $cs->registerCoreScript('jquery');


        if (isset($htmlOptions['submit'])) {

            $cs->registerCoreScript('yii');

            $request = Yii::app()->getRequest();

            if ($request->enableCsrfValidation && isset($htmlOptions['csrf']) && $htmlOptions['csrf'])

                $htmlOptions['params'][$request->csrfTokenName] = $request->getCsrfToken();

            if (isset($htmlOptions['params']))

                $params = CJavaScript::encode($htmlOptions['params']);

            else

                $params = '{}';

            if ($htmlOptions['submit'] !== '')

                $url = CJavaScript::quote(self::normalizeUrl($htmlOptions['submit']));

            else

                $url = '';

            $handler.="jQuery.yii.submitForm(this,'$url',$params);{$return};";

        }


        if (isset($htmlOptions['ajax']))

            $handler.=self::ajax($htmlOptions['ajax']) . "{$return};";


        if (isset($htmlOptions['confirm'])) {

            $confirm = 'confirm(\'' . CJavaScript::quote($htmlOptions['confirm']) . '\')';

            if ($handler !== '')

                $handler = "if($confirm) {" . $handler . "} else return false;";

            else

                $handler = "return $confirm;";

        }


        if ($live)

            $cs->registerScript('Yii.CHtml.#' . $id, "jQuery('body').on('$event','#$id',function(){{$handler}});");

        else

            $cs->registerScript('Yii.CHtml.#' . $id, "jQuery('#$id').on('$event', function(){{$handler}});");

        unset($htmlOptions['params'], $htmlOptions['submit'], $htmlOptions['ajax'], $htmlOptions['confirm'], $htmlOptions['return'], $htmlOptions['csrf']);

    }


    public static function ajax($options)

    {

        Yii::app()->getClientScript()->registerCoreScript('jquery');

        if (!isset($options['url'])) {

            $options['url'] = new CJavaScriptExpression($options['url']);

        } else {

//            $options['url'] = self::normalizeUrl($options['url']);

            $options['url'] = new CJavaScriptExpression('(jQuery(this).val() == ' . $options['data-specific-id'] . ' ? "' . self::normalizeUrl($options['data-url'])

                            . '" : "' . self::normalizeUrl($options['url']) . '")');

        }




        if (!isset($options['cache']))

            $options['cache'] = false;

        if (!isset($options['data']) && isset($options['type']))

            $options['data'] = new CJavaScriptExpression('jQuery(this).parents("form").serialize()');

        foreach (array('beforeSend', 'complete', 'error', 'success') as $name) {

            if (isset($options[$name]) && !($options[$name] instanceof CJavaScriptExpression))

                $options[$name] = new CJavaScriptExpression($options[$name]);

        }

        if (isset($options['update'])) {

            if (!isset($options['success']))

                $options['success'] = new CJavaScriptExpression('function(html){jQuery("' . $options['update'] . '").html(html)}');

            unset($options['update']);

        }

        if (isset($options['replace'])) {

            if (!isset($options['success']))

                $options['success'] = new CJavaScriptExpression('function(html){jQuery("' . $options['replace'] . '").replaceWith(html)}');

            unset($options['replace']);

        }

        return 'jQuery.ajax(' . CJavaScript::encode($options) . ');';

    }


}



Please notice I changed


jQuery(this).val() == 4

to


jQuery(this).val() == $options['data-specific-id']

YOu’ll need to add ‘data-specific-id’ option to ajax configuration with an item ID that needs a specific treatment - I think this way it looks better (more reusable/editable)

oh my GOD, it’s really awesome. I learn a new thing. Many many thanks to you. :D Is there any way make the update depending on URL rather success although I can do.

Welcome :)

Please clarify your question.

BTW, it’s usually better to remove a quote text (or leave only small parts of it) from you response not to have all the thread filled with quotes texts.

At first, after getting the result from controller, I updated the form id as


'update'=>'#'.CHtml::activeId($model, 'package_id'),

using ajax update. Now I have to accomplish the task using ajax success. Is it not possible to use ajax update. However, if not, how can accomplish this task using ajax success. Can you give an idea? Actually, I am little week in ajax. I have tried as:

in controller:




public function actionServicePackage()

    {

        $data=Servicepackage::model()->findAll('service_id=:service_id',

            array(':service_id'=>(int) $_POST['id']));


        $data=CHtml::listData($data,'id','name');

        echo "<option value=''>Select a Package</option>";

        foreach($data as $value=>$name)  {

            echo CHtml::tag('option',

                array('value'=>$value),CHtml::encode($name),true);

        }

    }



now I am confused how to write code in success to update. Please help me or let me know if my explanation is not adequate.

Thanks again.

First of all, if you’re not very familiar with ajax, I would suggest you to read jquery ajx() documentation.

If you use ‘dataType’=>‘json’, then you may send send several variables from a server to a ajax success handler, including a marker, which controller method was called.

This is shown in the example I wrote before.

You may send the rest of a data in a similar manner:




$model = new Users();

echo MyHtml::activedropDownList($model, 'name', CHtml::listData($model->findAll(), 'id', 'name'), array(

    'class' => 'form-control',

    'prompt' => 'Select a Service',

    'ajax' => array(

        'data-url'=>$this->createUrl('serviceRegistration'),

        'url' => $this->createUrl('servicePackage'), // it is selected at MyHtml::ajax() which URL to use

        'type' => 'POST',

        'dataType' => 'json',

        'success' => 'function(data){

            if(data.handler == "registration"){// or data.registration, if controller response looked like json_encode(array('registration'=>1, 'response'=>$options));

                $("#update-registration").html(data.response);

            }else if(data.handler == "package"){// or data.package, if controller response looked like json_encode(array('package'=>1, 'response'=>$options));

                $("#update-package").html(data.response);

            }

        }',

        'data' => array('id' => 'js:this.value'),

    )

    ));

// controller

    public function actionServiceRegistration()

    {

        $data=Users::model()->findAll();

        $options[] = 'Select Registration';

        $options = array_merge($options, CHtml::listData($data,'id','name'));

        $htmlOptions = array();

        $options = CHtml::listOptions('', $options, $htmlOptions);

        echo json_encode(array('handler'=>'registration', 'response'=>$options));

    }

    public function actionServicePackage()

    {

        $data=Users::model()->findAll();

        $options[] = 'Select a Package';

        $options = array_merge($options, CHtml::listData($data,'id','name'));

        $htmlOptions = array();

        $options = CHtml::listOptions('', $options, $htmlOptions);

        echo json_encode(array('handler'=>'package', 'response'=>$options));

    }

It’s really interesting that it works nicely here. Yes I should learn little more jquery ajax. However, a lot of thanks to you that staying with me and help me for few days, finally I got the solution. :D

cool, welcome :)