selectボックスの選択した値に応じて、別の入力欄に入力がされているかをチェックするようなバリデータを実装したい。
Yiiのエクステンションがあるか探してみたんだけど、phpのバリデータのみで、クライアントサイドのバリデートもするものは見つからなかったので、ちょっと作ってみた。
要件としては
- phpだけでなく、クライアントサイド用のJavascriptのバリデータも必要
- selectボックスのデフォルト値は「利用する」とする
- selectボックスが「利用しない」の場合のみ、別の入力欄は必須とする
となります。
まず、以下のようなselectボックスとしてuse_ej、入力欄としてexdataがあるフォームを用意します。
<?php /* @var $model Event */ /* @var $form CActiveForm */ ?> <div class="form"> <?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm', array( 'id'=>'event-form', 'clientOptions'=>array('validateOnSubmit'=>true), )); ?> <p class="help-block">「 <span class="required">*</span> 」のある欄は入力必須です。</p> <?php echo $form->errorSummary($model); ?> <?php echo $form->dropDownListRow($model,'use_ej', $model->getUseEjArray(),array('class'=>'span5')); ?> <?php echo $form->textAreaRow($model,'exdata',array('class'=>'span5')); ?> <div class="form-actions"> <?php $this->widget('bootstrap.widgets.TbButton', array( 'buttonType'=>'submit', 'type'=>'primary', 'label'=>'登録', )); ?> </div> <?php $this->endWidget(); ?> </div><!-- form -->
次にモデルを用意します。
バリデートのルールとして、use_ejは入力必須、exdataはuse_ejの値がself::EVENT_USE_EJ_NOの場合のみ必須となるようにします。
<?php class Event extends CActiveRecord { const EVENT_USE_EJ_YES=1; const EVENT_USE_EJ_NO=2; public $use_ej_array=array( self::EVENT_USE_EJ_YES => '利用する', self::EVENT_USE_EJ_NO => '利用しない', ); public function rules() { // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( array('use_ej', 'required'), array('exdata', 'ext.MyValidators.exData', 'field'=>'use_ej', 'value'=>self::EVENT_USE_EJ_NO, 'message'=>'「利用しない」を選んだ場合は入力してください。'), ); } public function getUseEjArray() { return $this->use_ej_array; } }
protected/extensions/MyValidators/exDataの内容は以下のような感じです。
バリデータ作成については、「Yiiでvalidatorを作成する。」を見てください。
<?php class exData extends CValidator { public $field; public $value; private $_message='{attribute} を入力してください。'; /** * Validates the attribute of the object. * If there is any error, the error message is added to the object. * @param CModel $object the object being validated * @param string $attribute the attribute being validated */ protected function validateAttribute($object,$attribute) { $f=null; // check the strength parameter used in the validation rule of our model if (!empty($this->field) && isset($object->{$this->field})) $f=$object->{$this->field}; if(!empty($f) && $this->value==$f) { // extract the attribute value from it's model object $value=$object->$attribute; if(empty($value)) { $message=$this->message!==null?$this->message:Yii::t('yii',$this->_message); $this->addError($object,$attribute,$message); } } } /** * Returns the JavaScript needed for performing client-side validation. * @param CModel $object the data object being validated * @param string $attribute the name of the attribute to be validated. * @return string the client-side validation script. * @see CActiveForm::enableClientValidation */ public function clientValidateAttribute($object,$attribute) { $message=$this->message; $condition="jQuery.trim(value)=='' && jQuery.trim(jQuery('#".get_class($object).'_'.$this->field."').val())=='".$this->value."'"; if($message===null) $message=Yii::t('yii',$this->_message); return " if(".$condition.") { messages.push(".CJSON::encode($message)."); } "; } }
ただ、この場合、
- use_ejで「利用しない」を選び、exdataで未入力のエラーを出す。
- その後、use_ejで「利用する」を選ぶ。
ということをしてもexdataのエラーは消えないということが起こります。
モデルのrules関数にuse_ejに新しいルールを用意しようと思ったんですが、そうするとuse_ejのほうに期待しないエラーが表示されることになります。
で、結局、うまい方法がわからなかったので、ビューに、「use_ejの値が変わったら、exdataフィールドのみバリデートする」というJavascriptを追加することにしました。
Javascriptを追加したビューは以下のような感じです。
<?php /* @var $model Event */ /* @var $form CActiveForm */ Yii::app()->clientScript->registerScript('useej', " var getAFValue = function (o) { var type, c = []; if (!o.length) { return undefined; } if (o[0].tagName.toLowerCase() === 'span') { o.find(':checked').each(function () { c.push(this.value); }); return c.join(','); } type = o.attr('type'); if (type === 'checkbox' || type === 'radio') { return o.filter(':checked').val(); } else { return o.val(); } }; $('#Event_use_ej').change(function(){ var form=$('#event-form'); var settings=form.data('settings'); var messages = {}; $.each(settings.attributes, function (i) { if(this['id']=='Event_exdata'){ var value, msg = []; value = getAFValue(form.find('#' + this['inputID'])); this.clientValidation(value, msg, this); if (msg.length) { messages[this['id']] = msg; } if (this.status) { $.fn.yiiactiveform.updateInput(this, messages, form); } } }); }); "); ?> <div class="form"> <?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm', array( 'id'=>'event-form', 'clientOptions'=>array('validateOnSubmit'=>true), )); ?> <p class="help-block">「 <span class="required">*</span> 」のある欄は入力必須です。</p> <?php echo $form->errorSummary($model); ?> <?php echo $form->dropDownListRow($model,'use_ej', $model->getUseEjArray(),array('class'=>'span5')); ?> <?php echo $form->textAreaRow($model,'exdata',array('class'=>'span5')); ?> <div class="form-actions"> <?php $this->widget('bootstrap.widgets.TbButton', array( 'buttonType'=>'submit', 'type'=>'primary', 'label'=>'登録', )); ?> </div> <?php $this->endWidget(); ?> </div><!-- form -->
なにか、スッキリする方法があれば教えて下さい。