Yiiで入力があったら他のフィールドのバリデートも行うようにする方法

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).");
  }
  ";
  }
}

ただ、この場合、

  1. use_ejで「利用しない」を選び、exdataで未入力のエラーを出す。
  2. その後、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 -->

なにか、スッキリする方法があれば教えて下さい。

投稿日:
カテゴリー: php タグ: