CActiveForm->radioButtonList() not working as intended with Client Side Validation

I am having the same problem as described here in Issue 1356. I am unable to post a link because this is one of my first posts.

In short, when I use client-side validation, the validator always tells me that radioButtonList values are always blank, no matter what. For some reason, my ‘validateOnSubmit’ was working earlier for this form, but is not working now. I have been doing a lot of tinkering, so I unfortunately lost track of what made that break. Right now I am just running Yii 1.1.8r3324, unpatched (as it can be downloaded on the main Yii download page)

NB: I am using the simple-modal jQuery plugin as provided by Eric Martin. I am unable to post a link to the plugin because this is one of my first posts. This has had no impact on my code thusfar, that I can tell, but it is worth noting.

Here are the two main problems:

  1. Client Side Validation always produces the error “{attribute} cannot be blank” when using a radioButtonList on an attribute that is ‘required’ (i.e. using the CRequiredValidator).

  2. When the ‘required’ validator is not used, but a ‘numerical’ validator is used instead, Client Side Validation does not work on its own. That is ‘validateOnSubmit’=>true does not suppress the submit action when the submit button is pressed and validation fails, making Client Side Validation useless.

AJAX seems to be working properly, though. As such, I am using AJAX in my code until this bug is fixed. It is not ideal for my application, but can work for now. Below is my code NOT using AJAX validation and WITHOUT the ‘required’ validator on the rating attribute of the reviews model.

_verifyAndReviewForm.php - a view file


<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'merchant_review_box',

	'enableAjaxValidation'=>false,

    'enableClientValidation'=>true,

    'clientOptions' => array( 'validateOnSubmit'=>true ),

)); ?>

    <div class="modal_body">

        <div class="food_quality_company_info clearfix">

            <div class="modal_caption">

                <p>Food quality verifications are the lifeblood of Harvestopia. Only verify what you know for sure, and don't worry about the rest - you can always go back and verify later! </p>

            </div>

            <p class="note">Fields with <span class="required">*</span> are required.</p>

            <dl class = "col0">

                <dt>Meat/Egg Quality</dt>


                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->meatEggVerifications,'pastured_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->meatEggVerifications,'pastured_verifications'); ?>

                    <?php echo $form->error($merchant->meatEggVerifications,'pastured_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->meatEggVerifications,'cage_free_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->meatEggVerifications,'cage_free_verifications'); ?>

                    <?php echo $form->error($merchant->meatEggVerifications,'cage_free_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->meatEggVerifications,'wild_game_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->meatEggVerifications,'wild_game_verifications'); ?>

                    <?php echo $form->error($merchant->meatEggVerifications,'wild_game_verifications'); ?>

                </dd>

            </dl>


            <dl class = "col1">

                <dt>Locality</dt>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->localityVerifications,'within_10_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->localityVerifications,'within_10_verifications'); ?>

                   <?php echo $form->error($merchant->localityVerifications,'within_10_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->localityVerifications,'within_100_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->localityVerifications,'within_100_verifications'); ?>

                    <?php echo $form->error($merchant->localityVerifications,'within_100_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->localityVerifications,'within_1000_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->localityVerifications,'within_1000_verifications'); ?>

                    <?php echo $form->error($merchant->localityVerifications,'within_1000_verifications'); ?>

                </dd>

            </dl>


            <dl class = "col2">

                <dt>Organic Products</dt>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->organicVerifications,'meats_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->organicVerifications,'meats_verifications'); ?>

                    <?php echo $form->error($merchant->organicVerifications,'meats_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->organicVerifications,'eggs_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->organicVerifications,'eggs_verifications'); ?>

                    <?php echo $form->error($merchant->organicVerifications,'eggs_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->organicVerifications,'produce_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->organicVerifications,'produce_verifications'); ?>

                    <?php echo $form->error($merchant->organicVerifications,'produce_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->organicVerifications,'other_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->organicVerifications,'other_verifications'); ?>

                    <?php echo $form->error($merchant->organicVerifications,'other_verifications'); ?>

                </dd>

            </dl>

            <dl class = "col3">

                <dt>Lifestyle Options</dt>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->lifestyleOptionsVerifications,'paleo_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->lifestyleOptionsVerifications,'paleo_verifications'); ?>

                    <?php echo $form->error($merchant->lifestyleOptionsVerifications,'paleo_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->lifestyleOptionsVerifications,'vegetarian_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->lifestyleOptionsVerifications,'vegetarian_verifications'); ?>

                    <?php echo $form->error($merchant->lifestyleOptionsVerifications,'vegetarian_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->lifestyleOptionsVerifications,'vegan_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->lifestyleOptionsVerifications,'vegan_verifications'); ?>

                    <?php echo $form->error($merchant->lifestyleOptionsVerifications,'vegan_verifications'); ?>

                </dd>

                <dd class="info_box">

                    <?php echo $form->checkBox($merchant->lifestyleOptionsVerifications,'gluten_free_verifications', array('checked' => false)); ?>

                    <?php echo $form->labelEx($merchant->lifestyleOptionsVerifications,'gluten_free_verifications'); ?>

                    <?php echo $form->error($merchant->lifestyleOptionsVerifications,'gluten_free_verifications'); ?>

                </dd>

            </dl>

        </div>

        <div id="form_review_box" class="review_box">

            <div class="rating_and_desc_box clearfix">

                <?php echo $form->labelEx($review,'rating'); ?>

                <?php echo $form->error($review,'rating'); ?>

                <?php $this->widget('StarRatingInput', array( 'form' => $form,

                                                              'review' => $review ) ); ?>

                <span id="rating_description_1"></span>

            </div>

            <?php echo $form->labelEx($review,'review_text'); ?>

            <?php echo $form->error($review,'review_text'); ?>

            <?php echo $form->textArea($review,'review_text', array('class' => 'textarea')); ?>

        </div>

    </div>


	


	<div class="form_footer clearfix">

		<?php echo CHtml::submitButton('Submit', array('class' => 'b_button')); ?>

        <?php echo CHtml::link( 'Cancel', '#', array('class' => 'simplemodal-close b_button') );?>

	</div>


<?php $this->endWidget(); ?>


</div>

actionView from my Controller


public function actionView( $id = null )

	{

        //Only load the page if an ID is set.

        if( isset( $id ) ){

            //Set return URL

            Yii::app()->user->returnUrl = $this->createUrl('/merchants/view', array('id' => $id));;


            $loginForm = new LoginForm();


            // collect user input data

            if(isset($_POST['LoginForm']))

            {

                $loginForm->attributes=$_POST['LoginForm'];

                // validate user input and redirect to the previous page if valid

                if($loginForm->validate() && $loginForm->login())

                    $loginForm->redirect(Yii::app()->user->returnUrl);

            }


            //Review model needed to process review form.

            $review = new Reviews('reviewAndVerify');

            $merchant = Merchants::model()->findByPk($id);




            if(isset($_POST['ajax']) && $_POST['ajax']==='merchant_review_box')

            {

                echo CActiveForm::validate($review);

                Yii::app()->end();

            }


            //If the Reviews POST is set, then a review was submitted

            if( isset($_POST['Reviews']) ) {


                //Update the review information before saving

                $review->attributes = $_POST['Reviews'];

                $review->reviewer_id = Yii::app()->user->id;

                $review->merchant_id = $id;

                $review->review_date = date('Y-m-d H:i:s e');


                //If the save is successful....

                if( $review->save(true) ) {

                    //Increment Counters for all verifications if set

                    $merchant->meatEggVerifications->saveCounters( array( 'pastured_verifications'=> $_POST['MeatEggVerifications']['pastured_verifications'],

                                                                          'cage_free_verifications' => $_POST['MeatEggVerifications']['cage_free_verifications'],

                                                                          'wild_game_verifications' => $_POST['MeatEggVerifications']['wild_game_verifications'])

                                                                 );

                    $merchant->localityVerifications->saveCounters( array( 'within_10_verifications'=> $_POST['LocalityVerifications']['within_10_verifications'],

                                                                          'within_100_verifications' => $_POST['LocalityVerifications']['within_100_verifications'],

                                                                          'within_1000_verifications' => $_POST['LocalityVerifications']['within_1000_verifications'])

                                                                 );

                    $merchant->organicVerifications->saveCounters( array( 'meats_verifications'=> $_POST['OrganicVerifications']['meats_verifications'],

                                                                          'eggs_verifications' => $_POST['OrganicVerifications']['eggs_verifications'],

                                                                          'produce_verifications' => $_POST['OrganicVerifications']['produce_verifications'],

                                                                          'other_verifications' => $_POST['OrganicVerifications']['other_verifications'])

                                                                 );

                    $merchant->lifestyleOptionsVerifications->saveCounters( array( 'paleo_verifications'=> $_POST['LifestyleOptionsVerifications']['paleo_verifications'],

                                                                          'vegetarian_verifications' => $_POST['LifestyleOptionsVerifications']['vegetarian_verifications'],

                                                                          'vegan_verifications' => $_POST['LifestyleOptionsVerifications']['vegan_verifications'],

                                                                          'gluten_free_verifications' => $_POST['LifestyleOptionsVerifications']['gluten_free_verifications'],)

                                                                 );

                    // Clear the fields for display purposes

                    unset( $review->review_text );

                    unset( $review->rating );

                }

            }

            $this->merchant=$this->loadModel($id);


            $this->pageTitle = $this->merchant->name . ' | ' . Yii::app()->name;


            //Set up pagination

            $reviewsPerPage = 40;

            $criteria=new CDbCriteria(array('order'=>'review_date DESC'));

            $criteria->condition = 'merchant_id =' . $id;


		    $reviewsDataProvider=new CActiveDataProvider('Reviews', array(

			'pagination'=>array(

				'pageSize'=> $reviewsPerPage,

			),

			'criteria'=>$criteria,

            ));


            $this->render('view',array(

			                        'merchant'=>$this->merchant,

                                    'reviewsDataProvider' => $reviewsDataProvider,

                                    'review' => $review,

                                    'loginForm' => $loginForm,

                                      ));

        } else {

            //If no ID is set, don't load the page - redirect back to the merchant index.

            $this->redirect(array('merchants/index'));

        }

	}

Let me know if you need anything else.

From the fast look through your code…

You wrote that the problem is when you use the radioButtonList on an attribute that is required… but on the code you posted there is nowhere the radioButtonList.

My mistake – it is embedded in another widget…

StarRatingInput (NOT the same as CStarRating)


<?php

/**

 * StarRatingInput

 *

 * @author Chris Salvato

 * @copyright 2011 Harvestopia

 */




class StarRatingInput extends CInputWidget{


    public $form;


    public $review;


    protected $baseScriptUrl;


    public function registerClientScript()

    {

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

		$cs->registerCoreScript('jquery');

		$cs->registerCoreScript('bbq');

		$cs->registerScriptFile($this->baseScriptUrl.'/simplemodal.1.4.1.js');

		$cs->registerScriptFile($this->baseScriptUrl.'/interactive-form.js');

    }


    public function init()

    {

        if($this->baseScriptUrl===null)

            $this->baseScriptUrl=Yii::app()->getBaseUrl() . '/js';


        //Register the scripts needed to make this widget functional.

        $this->registerClientScript();

        

        echo '<fieldset class="form_rating">';

        echo '    <ul id="rating_box_1" class="form_rating_box clearfix">';

        echo '         <li>';

        echo $this->form->radioButtonList($this->review,

                                          'rating',

                                          array("1" => "I would never go back!",

                                                "2" => "Something just wasn't right...",

                                                "3" => "Good, but I wouldn't go out of my way...",

                                                "4" => "It was great!",

                                                "5" => "Hands down, the best."

                                          ),

                                          array('uncheckValue' => '0',

                                                'separator' => '</li><li>',

                                          )

                                         );

        echo '         </li>';

        echo '    </ul>';

        echo '</fieldset>';


        parent::init();

    }


}



It’s 01:04 here and I’m a bit sleepy now… but if I think right the radioButtonList displays it’s UL… so that will become a sublist of your UL…

That could be the possible problem why the client validation does not work for you… because by default the Yii JS code is looking for the closest DIV to the input element and you are not using it here…

check the documentation for inputContainer under clientOptions - http://www.yiiframework.com/doc/api/1.1/CActiveForm#clientOptions-detail

Thank you for the prompt replies, mdomba - you have been great to work with so far.

Here is the HTML code being generated by my view:




<fieldset class="form_rating">

    <ul id="rating_box_1" class="form_rating_box clearfix stars-undefined">

        <li>

            <input id="ytReviews_rating" type="hidden" value="0" name="Reviews[rating]">

            <input id="Reviews_rating_0" value="1" type="radio" name="Reviews[rating]">

            <label for="Reviews_rating_0">I would never go back!</label>

        </li>

        <li>

            <input id="Reviews_rating_1" value="2" type="radio" name="Reviews[rating]"> 

            <label for="Reviews_rating_1">Something just wasn't right...</label>

        </li>

        <li>

            <input id="Reviews_rating_2" value="3" type="radio" name="Reviews[rating]"> 

            <label for="Reviews_rating_2">Good, but I wouldn't go out of my way...</label>

        </li>

        <li>

            <input id="Reviews_rating_3" value="4" type="radio" name="Reviews[rating]"> 

            <label for="Reviews_rating_3">It was great!</label>

        </li>

        <li>

            <input id="Reviews_rating_4" value="5" type="radio" name="Reviews[rating]"> 

            <label for="Reviews_rating_4">Hands down, the best.</label>

        </li>

    </ul>

</fieldset>



It looks like if I want this to work, I need to use the latest nightly build and try to change the inputContainer to ‘li’…let me know if I misunderstand. I am working on a different problem at the moment, but will try that out ASAP.

Yes, you need the last build… the inputContainer should be UL as this is the container of the whole list… and instead of your "hack" with the separator you need to use the template…

So it would be




echo "<ul>";

$this->form->radioButtonList($this->review, 'rating', array(...), array(

      'uncheckValue' => '0',

      'separator' => '',

      'template' => '<li>{input}{label}</li>'

   ));

echo "</ul>";



By using template you can get rid of the first <li> and last </li>

And of course when calling the error you need to specify the inputContainer… something like


$form->error($model,attribute,array('inputContainer'=>'ul'));

Thank you so much for the help. This worked brilliantly. Thank you for bearing with me - your help has been extremely useful. I apologize if I was a little thick before :)