В данном маленьком топике я хотел бы рассказать об одном очень простом рецепте (который многим из вас, вероятно знаком) в контексте фреймворка Yii. Речь идет о динамическом изменении правил валидации формы — когда правила валидации изменяются в зависимости от выбора пользователя вашего приложения, сделанного, например, путем выбора значения из списка или установленного чекбокса.
На применение данного решения меня натолкнула практическая необходимость при написании одного простенького Контакт-Центра с веб-интерфейсом. Чтоб не придумывать примеры, вкратце поясню применение данного рецепта на примере этого же КЦ. Как известно, основная деятельность операторов КЦ заключается в приеме звонков от клиентов и в исходящих звонках клиентам. По результатам звонка оператор должен занести в систему определенные данные — результат разговора с клиентом, на основании которого определяется статус тикета и дальнейшая логика работы с ним (назначение повторного звонка, любой другой процедуры, закрытие тикета и т.д.). Проблема заключается в том, что некоторые поля формы (читай — атрибуты модели) должны быть заполнены или незаполнены в зависимости от «родительского» статуса. Например, очевидно, что поле «Результат разговора с клиентом» не имеет смысла заполнять, если технический статус звонка — «Недозвон/Неверный номер» ( технический статус должен выставлять оператор, а не система, поскольку звонок может состояться, но получателем звонка ВДРУГ может оказаться не клиент — ошибка в номере и т.д. ). В то же время, если выставлен технический статус «Успешный дозвон» — поле «Результат разговора с клиентом» обязательно должно быть заполнено. Снова-таки: в зависимости от результата разговора может появиться необходимость в заполнении дополнительных полей — например поля «Дата повторного звонка» при статусе «Повторный звонок».
Естественно, я хотел бы, чтобы в зависимости от значения определенного поля формы менялись и правила валидации для всей формы. На практике эта задача очень проста в применении, но очень полезна в таких случаях, как мой.
Итак:
1. Создаем несколько сценариев для вадидации формы звонка.
Сначала создадим правила для тех полей, которые необходимы во всех сценариях:
Думаю первое правило не вызывает вопросов — технический статус звонка — это значение верхнего уровня в форме — оно необходимо в любом сценарии.
Продолжим идти от общего к частно��у:
Далее, определяем правила для более частных случаев:
Таким образом мы можем перебрать все необходимые правила валидации — как в общих случаях, так и в каждом частном. Дело остается за малым: сделать так, чтобы правила валидации менялись в зависимости от выбранных значений в форме. Как вы уже наверное догадались, для этого мы будем использовать CActiveForm, так как этот виджет из коробки позволяет просто и ясно проводить ajax-валидацию.
Создадим для примера простенькую форму:
Из интересных нам опций необходимо отметить такие: 'enableAjaxValidation', 'validateOnChange','validateOnSubmit'. Полагаю, предназначение этих опций не вызывает вопросов и понятно из их названий. Продолжим:
Не буду утомлять вас большим количеством полей, думаю трех и так достаточно для примера. Все, что нам осталось сделать, — это собственно реализовать функционал для динамического изменения правил валидации (читай — сценария).
На самом деле, в основе этого функционала лежит простейшая логика: нужно каждому значению поля сопоставить сценарий, по которому будет валидироваться форма после выбора этого значения (в данном случае при выборе значения из списка, созданного методом CActiveForm::dropDownList() ). Это очень простое решение, и я не стану зацикливаться на деталях и создавать реализацию в ООП-манере, ведь главное донести идею.
Вместо этого я для простоты помещу соответствующий код прямо в метод performAjaxValidation() класса контроллера. Ниже немного измененный код стандартного метода performAjaxValidation() и метода, который принимает форму:
Как вы видите из кода, все, что я делаю — это просто меняю сценарий в зависимости от пришедшего значения поля 'call_status'. Теперь, если оператор выберет в форме статус дозвона «Успешный дозвон», сценарий модели при валидации изменится на 'successCallSubmit', который ожидает от формы теперь еще и статус разговора — в результате, выбрав «Успешный дозвон», оператор уже не сможет засабмитить форму, пока не заполнит поле «Результат разговора». В то же время, если статус дозвона будет «Неправильный номер», форма засабмитится без всяких дополнительных полей. По тому же принципу можно действовать и далее — со статусами разговоров и их дополнительными полями и т.д.
Применение такого подхода будет полезным в таких ситуациях, когда количество и виды необходимых для заполнения полей будут меняться в зависимости от выбора пользователя. Прошу прощения за немного каламбурное изложение, и успехов вам в разработке на замечательном фреймворке Yii.
На применение данного решения меня натолкнула практическая необходимость при написании одного простенького Контакт-Центра с веб-интерфейсом. Чтоб не придумывать примеры, вкратце поясню применение данного рецепта на примере этого же КЦ. Как известно, основная деятельность операторов КЦ заключается в приеме звонков от клиентов и в исходящих звонках клиентам. По результатам звонка оператор должен занести в систему определенные данные — результат разговора с клиентом, на основании которого определяется статус тикета и дальнейшая логика работы с ним (назначение повторного звонка, любой другой процедуры, закрытие тикета и т.д.). Проблема заключается в том, что некоторые поля формы (читай — атрибуты модели) должны быть заполнены или незаполнены в зависимости от «родительского» статуса. Например, очевидно, что поле «Результат разговора с клиентом» не имеет смысла заполнять, если технический статус звонка — «Недозвон/Неверный номер» ( технический статус должен выставлять оператор, а не система, поскольку звонок может состояться, но получателем звонка ВДРУГ может оказаться не клиент — ошибка в номере и т.д. ). В то же время, если выставлен технический статус «Успешный дозвон» — поле «Результат разговора с клиентом» обязательно должно быть заполнено. Снова-таки: в зависимости от результата разговора может появиться необходимость в заполнении дополнительных полей — например поля «Дата повторного звонка» при статусе «Повторный звонок».
Естественно, я хотел бы, чтобы в зависимости от значения определенного поля формы менялись и правила валидации для всей формы. На практике эта задача очень проста в применении, но очень полезна в таких случаях, как мой.
Итак:
1. Создаем несколько сценариев для вадидации формы звонка.
Сначала создадим правила для тех полей, которые необходимы во всех сценариях:
public function rules(){ //... array('call_status', 'required', 'on'=>'callSubmit, invalidNumberSubmit, validCallSubmit, successCallSubmit...' //... 'message'=>'Поле "{attribute}" не может быть пустым.'), }
Думаю первое правило не вызывает вопросов — технический статус звонка — это значение верхнего уровня в форме — оно необходимо в любом сценарии.
Продолжим идти от общего к частно��у:
//.. Inside rules() method array('talk_status', 'required', 'on'=>'successCallSubmit, secondCall,...') /* здесь список сценариев, связанных с успешным дозвоном и статусами разговора */
Далее, определяем правила для более частных случаев:
//.. Inside rules() method array('new_call_date', 'required', 'on'=>'secondCall') /*дата повторного звнока при статусе "Повторный звонок"*/
Таким образом мы можем перебрать все необходимые правила валидации — как в общих случаях, так и в каждом частном. Дело остается за малым: сделать так, чтобы правила валидации менялись в зависимости от выбранных значений в форме. Как вы уже наверное догадались, для этого мы будем использовать CActiveForm, так как этот виджет из коробки позволяет просто и ясно проводить ajax-валидацию.
Создадим для примера простенькую форму:
<?php $form=$this->beginWidget('CActiveForm', array( 'id'=>'call-submit-form', 'enableAjaxValidation'=>true, 'clientOptions'=>array( 'validateOnChange'=>true, 'validateOnSubmit'=>true ), //всяческие настройки виджета )); ?>
Из интересных нам опций необходимо отметить такие: 'enableAjaxValidation', 'validateOnChange','validateOnSubmit'. Полагаю, предназначение этих опций не вызывает вопросов и понятно из их названий. Продолжим:
<div class='control-group'> <?php echo $form->labelEx($model,'call_status'); ?> <?php echo $form->dropDownList($model,'call_status', CHtml::listData( CallStatuses::model()->findAll(),'id','title' ) ); ?> <?php echo $form->labelEx($model,'talk_status'); ?> <?php echo $form->dropDownList($model,'talk_status', CHtml::listData(TalkStatuses::model()->findAll(),'id','title' ), )); ?> <?php echo $form->error($model,'call_status'); ?> <?php echo $form->error($model,'talk_status'); ?> </div> <div class='control-group'> <?php echo $form->label($model,'new_call_date'); ?> <?php //Поле выбора даты повторного звонка $this->widget('zii.widgets.jui.CJuiDatePicker', array( 'attribute'=>'new_call_date', 'model'=>$model, //... ), )); ?> </div>
Не буду утомлять вас большим количеством полей, думаю трех и так достаточно для примера. Все, что нам осталось сделать, — это собственно реализовать функционал для динамического изменения правил валидации (читай — сценария).
На самом деле, в основе этого функционала лежит простейшая логика: нужно каждому значению поля сопоставить сценарий, по которому будет валидироваться форма после выбора этого значения (в данном случае при выборе значения из списка, созданного методом CActiveForm::dropDownList() ). Это очень простое решение, и я не стану зацикливаться на деталях и создавать реализацию в ООП-манере, ведь главное донести идею.
Вместо этого я для простоты помещу соответствующий код прямо в метод performAjaxValidation() класса контроллера. Ниже немного измененный код стандартного метода performAjaxValidation() и метода, который принимает форму:
//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(); } }
Как вы видите из кода, все, что я делаю — это просто меняю сценарий в зависимости от пришедшего значения поля 'call_status'. Теперь, если оператор выберет в форме статус дозвона «Успешный дозвон», сценарий модели при валидации изменится на 'successCallSubmit', который ожидает от формы теперь еще и статус разговора — в результате, выбрав «Успешный дозвон», оператор уже не сможет засабмитить форму, пока не заполнит поле «Результат разговора». В то же время, если статус дозвона будет «Неправильный номер», форма засабмитится без всяких дополнительных полей. По тому же принципу можно действовать и далее — со статусами разговоров и их дополнительными полями и т.д.
Применение такого подхода будет полезным в таких ситуациях, когда количество и виды необходимых для заполнения полей будут меняться в зависимости от выбора пользователя. Прошу прощения за немного каламбурное изложение, и успехов вам в разработке на замечательном фреймворке Yii.
