jQuery and Required form fields

Here you go:




public function actionForm()

{

	$model=new Enquiry;

	$allErrors=array();

	

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

	{

		$scenario=$_POST['vehicle_type'];

		$model->setScenario($scenario);

		$model->validate();

		$allErrors[]=$model->getErrors();


	}

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

	{

		$scenario=$_POST['journey_from'];

		$model->setScenario($scenario);

		$model->validate();

		$allErrors[]=$model->getErrors();

	}

	

	$model->addErrors($allErrors);


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

	{

		$model->attributes=$_POST['Enquiry'];

			

		if($model->validate())

		{

			// processing here

		}

	}

	

	$this->render('index', array('model'=>$model));

}



At the moment your building an array of arrays which the model doesn’t understand when trying to show the error messages.

try this instead!


$allErrors += $model->getErrors();

If this evaluates to true


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

then your back to square one because your calling validate which will reset the errors again unless that is what you want to happen.

You are validating the model (up to twice) before injecting it with the POST variables. Then it looks like you are validating it a third time after injecting it with post variables

correct! you need to assign the values before calling validate() else it has no data to validate!

e.g.




$model = new Model;

$model->setScenario("car"); //in your case its coming from $_POST

$model->vehicle_type = $_POST['vehicle_type;'];

$mode->validate();


$model = new Model;

$model->setScenario("plane"); //in your case its coming from $_POST

$model->journey_from = $_POST['journey_from'];

$model->journey_to = $_POST['journey_to'];

$mode->validate();



or




$model = new Model;

$model->attributes=$_POST['Enquiry'];

$model->setScenario($_POST['vehicle_type']);

$mode->validate();



It has still not worked I’m afraid. Here is my new code:




public function actionForm()

{

	$model=new Enquiry;

	

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

	{

		$scenario=$_POST['vehicle_type'];

		$model->setScenario($scenario);

		$model->vehicle_type=$_POST['vehicle_type'];

		$model->validate();

	}

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

	{

		$scenario=$_POST['journey_from'];

		$model->setScenario($scenario);

		$model->journey_from=$_POST['journey_from'];

		$model->validate();

	}


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

	{

		$model->attributes=$_POST['Enquiry'];

		

		if($model->validate())

		{

			// processing here

		}

	}

	

	$this->render('index', array('model'=>$model));

}



Again, it only validates the most recent scenario.

Not sure if this helps, but those two model attributes are declared as $public in my model (they do not exist in the DB).

I think you’ve taken one step forward and one back or maybe just confused?

you need to add the latest changes you’ve made to the code you posted here http://www.yiiframework.com/forum/index.php?/topic/10483-jquery-and-required-form-fields/page__view__findpost__p__51697

and also make the changes as suggested here (the way you add to the array)

http://www.yiiframework.com/forum/index.php?/topic/10483-jquery-and-required-form-fields/page__view__findpost__p__51699

Bro just modify my code above and post a revised solution, as I’m not sure if I’m putting code in the wrong place or if I’ve gone totally off track here becuase I never expected it to be this much of a PITA!

I was hoping you’d solve it dude otherwise theres no fun in learning! It should go something like…




  $model=new Enquiry;

  $model->attributes=$_POST['Enquiry'];

  $allErrors=array();

 

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

  {

    $scenario=$_POST['vehicle_type'];

    $model->setScenario($scenario);

    $model->vehicle_type=$_POST['vehicle_type'];

    $model->validate();

    $allErrors += $model->getErrors();

  }


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

  {

    $scenario=$_POST['journey_from'];

    $model->setScenario($scenario);

    $model->journey_from=$_POST['journey_from'];

    $model->validate();

    $allErrors += $model->getErrors();

  }


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

  {

    if($model->validate())

    {

      // processing here

    } else {

    $allErrors += $model->getErrors();

    }

  }


  if (!empty($allErrors)) $model->addErrors($allErrors);


  $this->render('index', array('model'=>$model));



this is completely untested for obvious reasons and probably still needs tweaking…as I’m not quite sure what your trying to do other than test multiple scenarios.

Bro that just totally f’d it up. The error messages appeared twice and it still did not work. So I will now re-post the whole scenario (excuse the pun):

form.php:




<input value="Car" type="radio" name="vehicle_type" /> <!--Radio Button 'vehicle_type' Value 'Car' -->

<input value="Boat" type="radio" name="vehicle_type" /> <!-- Radio Button 'vehicle_type' Value 'Boat' -->


<input type="text" name="Enquiry[car_make]" /> <!-- Input Field 'Car Make' - this is REQUIRED when 'vehicle_type' = 'Car' -->


<select name="journey_from"> <!-- Select Dropdown 'Journey From' -->

<option value="Home">Home</option> <!-- Option Value 'Home' -->

<option value="Other">Other</option> <!-- Option Value 'Other' -->

</select>


<input type="text" name="Enquiry[return_datetime]" /> <!-- Input Field 'Return Date/Time' - this is REQUIRED when 'journey_from' = 'Home' -->



Model:




public $car_make;

public $return_datetime;


array('car_make', 'required', 'on'=>'Car'),


array('return_datetime', 'required', 'on'=>'Home'),



Basically I need both these rules to work in conjunction. So in other words, there are multiple scenarios that can be in effect when the form is posted.

This is how I am testing: I select ‘vehicle_type’ = ‘Car’ and ‘journey_from’ = ‘Home’. I leave the two required text fields blank and submit the form. Only the ‘return_datetime’ field is showing as an error.

Look at this line, what could be wrong




<select name="journey_from">



there is something wrong with name, compare it to return_datetime! also rather than writing out HTML I’d suggest using the form builder, that way things like this won’t happen, plus I think you will run into further problems e.g like remembering user selected options/values …

if you have a rule without the "on" option set it will run for ALL scenarios, thats why your probably getting duplicate errors, you can either change the rules or use the array _unique method to remove duplicate errors.

If you don’t quite understand scenarios then its probably easier to use the afterValidate callback with some if/else statements




class Enquiry ... {


public afterValidate(){


  if ($this->vehicle_type == 'Car')

  {

     //if some logic fails then use

     $this->addError("car_make", "car make is required");

  }


}


}




then your controller only needs




  $model=new Enquiry;

  $model->attributes=$_POST['Enquiry'];


   if ($model->save()) //success

   {

    //business logic - probably redirect

   }


  //else in the view show the error messages by looping through ->getErrors() or using http://www.yiiframework.com/doc/api/CActiveForm#errorSummary-detail

  $this->render('index', array('model'=>$model));




<select name="journey_from">

That isn’t the problem because that’s not a model attribute, and also it works perfectly fine when it is the only scenario in the controller. The problem only occurs where there is more than one scenario.

Just to be absolutely certain I’ve converted it into a module attribute and yep, still not working.

I think the problem lies in trying to check multiple scenarios - this is the difficulty I’m having. I’m not too sure whether manually adding in getErrors() and addErrors() is the solution, because surely the framework is going to handle that automatically.

This is definitely going down the wrong path, it’s surely a lot simpler to do than this.

my mistake, I must say you have way over-complicated your problem! you need to play about with scenarios, looks like you lack some understanding dude, someway to go yet!

Calling validate() resets the errors array every time. Yii will not handle it, I wouldn’t expect it to! one way you can avoid it, is by using afterValidate then simple if/else statements like I mentioned in my previous post. I don’t think you should be using multiple scenarios for such a simple problem! afterValidate callback should do the trick.

anyway try this




    $model=new Enquiry;

    

      $model->attributes=$_POST['Enquiry'];

      $allErrors=array();


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

      {

        $scenario=$_POST['vehicle_type'];

        $model->setScenario($scenario);

        $model->validate();

        $allErrors += $model->getErrors();

      }


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

      {

        $scenario=$_POST['journey_from'];

        $model->setScenario($scenario);

        $model->validate();

        $allErrors += $model->getErrors();

      }


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

      {

        $model->setScenario(""); //this probably needs to be insert or update

        if($model->validate())

        {

          // processing here

        } else {

        $allErrors += $model->getErrors();

        }

      }


      if (!empty($allErrors)) $model->addErrors($allErrors);


    $this->render('index',array('model'=>$model));



my result

with only home

778

test1.jpg

with home and car

777

test2.jpg

pretty busy tomorrow, so thats its for me now!

Hi Sniper,

That actually worked, however again I got duplicate errors which weren’t occurring before. Also I don’t agree that I’ve over-complicated the problem, as the code I posted a few posts up is very basic. I don’t fully understand scenarios, all I’ve merely done is attempted to extend the solution given by GDzyne in this post: http://www.yiiframework.com/forum/index.php?/topic/10483-jquery-and-required-form-fields/page__view__findpost__p__51515

I get the impression that the solution is not to actually have multiple scenarios, instead there must be a simpler solution which has probably been overlooked.

I don’t intend to use afterValidate() as the whole point is to use the built-in ‘rules’ functionality in order to specify which fields are required-dependent on another field/value.

I think I may have solved the problem with this simple solution:




array('car_make', 'checkRequiredCar'),


array('return_datetime', 'checkRequiredHome'),


public function checkRequiredCar()

{

	if($_POST['vehicle_type']=='Car' && empty($this->car_make))

	{

		$this->addError('car_make', 'Car Make cannot be blank.');

	}

}


public function checkRequiredHome()

{

	if($_POST['journey_from']=='Home' && empty($this->return_datetime))

	{

		$this->addError('return_datetime', 'Return Date/Time cannot be blank.');

	}

}



The downside to this is that those fields will not be rendered ‘required’ on page load (I have to manually apply the required label in the view) and error messages have to be manually defined.

Maybe not the definitive solution, but in terms of validation it works OK.

That works but its not the right way (its partly there), imho the $_POST should not be used directly inside the model, you should have just made those fields part of the Enquiry array! so instead you could go;




        if($this->vehicle_type=='Car' && empty($this->car_make))



It will make the code more reusable, what would happen if you manually wanted to test just once case? like wanted to make sure the car_make required during a different use case e.g. writing tests or a different situation you might not have thought of yet!

e.g. $mode->vehicle_type = ‘plane’; your solution will fail! you will start to understand more, once you get used to MVC but before you do that read more on how Models work dude!

One of your previous posts make me think (rightly or wrongly) you assume Yii has built-in solutions for all problems, don’t make that mistake! Yii is there to help you not to give you ready made solutions! rules only cover certain cases e…g require, range, boolean etc

Anyway it does look like that you came to some sort of conclusion as you created your own methods! it would have been similar to the afterValidate callback, remember its there to be used, why else would Yii have it?

Heres a simple solution which reduce the amount of code if you want to do more checks, this however does not cover other cases which you might want handling through rules) This could be improved allot but I don’t have the time…

This assumes vehicle_type and journey_from are part of the Enquiry array!




class Enquiry extends CActiveRecord

{

  //.....

  public function rules()

  {

    return array(

      array('car_make, return_datetime, vehicle_type, journey_from', 'safe'),

      array('car_make', 'validateDependOnAnotherField', 'dependOn' => 'vehicle_type',

        'ifValue' => 'Car'),

      array('return_datetime', 'validateDependOnAnotherField', 'dependOn' => 'journey_from', 

        'ifValue' => 'Home'),

    );

  }


  public function validateDependOnAnotherField($attribute, $params)

  {  

    if ((empty($this->$attribute) && $this->{$params['dependOn']} == $params['ifValue']))

    {

      $this->addError($attribute, $this->getAttributeLabel($attribute) . " - is required");

    }

  }

  //....

}



ideally Yii should have a "if" option for rules, probably a expression or callback which returns true of false (but only applies the rule if returning true)

e.g.




array('car_make', 'require', 'if' => '$data->vehicle_type == "Car"'),

//or

array('car_make', 'require', 'if' => 'methodNameWhichDoesASimilarThing'),



I’m only offering advice here, my suggestions aren’t the only solution/s, other people will have different/better solutions than me, thats just the way programming works! So what you have to manually add error messages, why did Yii provide you a method to do that? never to be used?

To me you did overcomplicate it dude!!! then why have you just simplified it by not using scenarios, huh?

Anyway I’ll get back to you again if need be!

How about this:


array('car_make', 'required'),


array('return_datetime', 'required'),


public function afterValidate()

{

	if($_POST['vehicle_type'] != 'Car')

	{

		$this->clearErrors('car_make');

		//'car_make' is required but the error will be cleared if 'vehicle_type' != 'Car'

	}

	

	if($this->journey_from !='Home')

	{

		$this->clearErrors('return_datetime');

		//'return_datetime' is required but the error will be cleared if 'journey_from' != 'Home'

	}

	

	return parent::afterValidate();

}

Can you foresee any problems at all?

Your solution of validation is correct, you can improve by using CRequiredValidator and avoiding to write the messages on your own:




public function rules()

{

    return array(

        array('car_make', 'carMakeCheck'),


    );

}


public function carMakeCheck()

{

  $validator=new CRequiredValidator;

  if ($this->vehicle_type=='car')

     $validator->validate($this, 'car_make')

      

}




This is the correct MVC approach, wich field should be validate is a question of ONLY the model, the controller should not change each time that you add a new vehicle_type.

Don’t use the AfterValidate, there are really no reason. You can override the isAttributeRequired function for display correctly the label:




public function isAttributeRequired($attribute)

{

   if ($attribute=='car_make')

       return true;

   [...other special field...]

   return parent::isAttributeRequired($attribute);

}



Hi, thanks zaccaria. That looks good. Although I’m just getting an error “Invalid argument supplied for foreach()” :




00169:     public function validate($object,$attributes=null)

00170:     {

00171:         if(is_array($attributes))

00172:             $attributes=array_intersect($this->attributes,$attributes);

00173:         else

00174:             $attributes=$this->attributes;

00175:         foreach($attributes as $attribute)

00176:         {

00177:             if(!$this->skipOnError || !$object->hasErrors($attribute))

00178:                 $this->validateAttribute($object,$attribute);

00179:         }

00180:     }



I created the model function exactly how you posted it.

Yes, is my mistake.

According to documentation the second parameter should be an array of attribute, not an attribute.

That’s what happens in answer to post without checking correctly the documentation )))

Try using:




$validator->validate($this, array('car_make'))



If your function is more complex, you can prepare the array and call validate only once.

I tried that, now I get error:

array_intersect(): Argument #1 is not an array