Back to Home

Yii: Dynamically change validation rules (scripts)

Yii · validation · cactiverecord

Yii: Dynamically change validation rules (scripts)

    In this small topic, I would like to talk about one very simple recipe (which many of you probably know) in the context of the Yii framework. It is a matter of dynamically changing the form validation rules - when the validation rules change depending on the choice of the user of your application, made, for example, by choosing a value from a list or an installed checkbox.

    The practical necessity prompted me to apply this solution when writing one simple Contact Center with a web interface. In order not to come up with examples, I will briefly explain the use of this recipe using the example of the same CC. As you know, the main activity of KC operators is to receive calls from customers and outgoing calls to customers. Based on the results of the call, the operator must enter certain data into the system - the result of a conversation with the client, on the basis of which the ticket status and further logic of working with it are determined (appointment of a repeated call, any other procedure, closing the ticket, etc.). The problem is that some form fields (read - model attributes) must be filled or empty depending on the “parent” status. For example, obviously that the “Result of a conversation with a client” field does not make sense to fill in if the technical status of the call is “Undelivered / Invalid number” (the operator should set the technical status, not the system, since the call may take place, but the client may suddenly be the recipient of the call - error in the room, etc.). At the same time, if the technical status “Successful dialing” is set, the “Result of conversation with the client” field must be filled out. Again: depending on the result of the conversation, it may be necessary to fill in additional fields - for example, the “Callback Date” field with the “Callback” status. but the recipient of the call may not be the client - an error in the number, etc. ) At the same time, if the technical status “Successful dialing” is set, the “Result of conversation with the client” field must be filled out. Again: depending on the result of the conversation, it may be necessary to fill in additional fields - for example, the “Callback Date” field with the “Callback” status. but the recipient of the call may not be the client - an error in the number, etc. ) At the same time, if the technical status “Successful dialing” is set, the “Result of conversation with the client” field must be filled out. Again: depending on the result of the conversation, it may be necessary to fill in additional fields - for example, the “Callback Date” field with the “Callback” status.

    Naturally, I would like that, depending on the value of a certain field of the form, the validation rules for the whole form also change. In practice, this task is very easy to use, but very useful in cases like mine.

    So:
    1. We create several scripts for the vadidation of the call form.
    First, create rules for those fields that are necessary in all scenarios:
    public function rules(){
        //...
       array('call_status', 'required', 'on'=>'callSubmit, invalidNumberSubmit, validCallSubmit, successCallSubmit...' //...
         'message'=>'Поле "{attribute}" не может быть пустым.'),
    }
    

    I think the first rule does not raise questions - the technical status of the call is the value of the top level in the form - it is necessary in any scenario.
    We continue to go from general to particular:
    //.. Inside rules() method
    array('talk_status',  'required', 'on'=>'successCallSubmit, secondCall,...') /* здесь список сценариев, связанных с успешным дозвоном и статусами разговора */
    

    Next, we define the rules for more special cases:
    //.. Inside rules() method
    array('new_call_date',  'required',	'on'=>'secondCall') /*дата повторного звнока при статусе "Повторный звонок"*/
    


    Thus, we can go through all the necessary validation rules, both in general cases and in each particular case. The matter remains small: make sure that the validation rules change depending on the selected values ​​in the form. As you probably already guessed, we will use CActiveForm for this, since this widget out of the box allows simple and clear ajax validation.
    Let's create a simple form for example:

    	beginWidget('CActiveForm', array(
    			'id'=>'call-submit-form',			
    			'enableAjaxValidation'=>true,			
    			'clientOptions'=>array(
    					'validateOnChange'=>true,
    					'validateOnSubmit'=>true
    					),
                           //всяческие настройки виджета
    )); ?>
    


    Of the options we are interested in, we should note the following: 'enableAjaxValidation', 'validateOnChange', 'validateOnSubmit'. I believe that the purpose of these options does not raise questions and is clear from their names. Continue:

    labelEx($model,'call_status'); ?> dropDownList($model,'call_status', CHtml::listData( CallStatuses::model()->findAll(),'id','title' ) ); ?> labelEx($model,'talk_status'); ?> dropDownList($model,'talk_status', CHtml::listData(TalkStatuses::model()->findAll(),'id','title' ), )); ?> error($model,'call_status'); ?> error($model,'talk_status'); ?>
    label($model,'new_call_date'); ?> widget('zii.widgets.jui.CJuiDatePicker', array( 'attribute'=>'new_call_date', 'model'=>$model, //... ), )); ?>


    I will not bore you with a large number of fields, I think three is enough for an example. All that remains for us to do is to actually implement the functionality for dynamically changing validation rules (read - scripts).

    In fact, the basis of this functionality is the simplest logic: you need to match each field value with a script that will validate the form after selecting this value (in this case, when choosing a value from the list created by the CActiveForm :: dropDownList () method). This is a very simple solution, and I will not dwell on the details and create an implementation in the OOP manner, because the main thing is to convey the idea.

    Instead, for simplicity, I put the appropriate code directly in the performAjaxValidation () method of the controller class. Below is the slightly modified code for the standard performAjaxValidation () method and the method that takes the form:

    //Inside controller 
    public function actionSaveCall(){
    /*Изначально создаем модель со сценарием 'callSubmit', который считает необходимым всего одно поле - 'call_status'  - технический статус звонка. */
    		$model=new Calls('callSubmit');
    		$this->performCallAjaxValidation($model);
    		if( isset($_POST['Calls']) ) {
    			$model->attributes = $_POST['Calls'];			
    			if( $model->save() )
    				$this->redirect( Yii::app()->user->returnUrl );			
    		}					
    	}
    protected function performAjaxValidation($model)
    	{
    		if(isset($_POST['ajax']) && $_POST['ajax']==='calls-form')
    		{
              $callStatusesScenarios = array( Calls::CALL_FAIL=>'validCallSubmit', Calls::SUCCESS_CALL=>'successCallSubmit', Calls::WRONG_NUMBER=>'invalidNumberSubmit');
    if( !empty($_POST['Calls']['call_status']) && !empty( $callStatusesScenarios[ $_POST['Calls']['call_status'] ] ) ){
    				$model->setScenario ( $callStatusesScenarios[ $_POST['Calls']['call_status'] ] );
    			}
    			echo CActiveForm::validate($model);
    			Yii::app()->end();
    		}
    	}	
    


    As you can see from the code, all I do is just change the script depending on the value of the 'call_status' field. Now, if the operator selects the “Successful dialer” dialer status in the form, the model scenario during validation changes to “successCallSubmit”, which now expects the conversation status from the form - as a result, by selecting “Successful dialer”, the operator will no longer be able to submit the form, until it completes the Conversation Result field. At the same time, if the dialer status is “Wrong number”, the form will be submitted without any additional fields. The same principle can be applied further - with the status of conversations and their additional fields, etc.

    The application of this approach will be useful in situations where the number and types of fields required to fill in will vary depending on the user's choice. I apologize for the little puns, and I wish you success in your development on the wonderful Yii framework.

    Read Next