jQuery and Required form fields

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

This one should work:




public function carMakeCheck()

{

  $validator=new CRequiredValidator;

  $validator->attributes= array('car_make');

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

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

      

}



Is much more complex than how I thought initialy, maybe CRequiredValidator is not thought to be used like that…

Perfect! That’s exactly the solution I’ve been after for the past 2-3 days!

No need for scenarios or adding errors in manually! :)

edit: was going to post an improvemnt but would have been fruitless :)

btw: solution had already been posted! you just failed to notice it! Imagine you had to check many more options! would you rather write 100 "if" statements or put them into rules?

Dude, super disapointed it took you so long, don’t worry it happens, 5 min problem into xx days has happend to many people, just don’t make a regular thing…

No worries bro, appreciate the help :)

n/p i’m sure you do!

BTW is there any reason you say not to use afterValidate() function? I think it makes it a lot easier as I can set the attribute as ‘required’ in the rules and then do a simple check for the condition for when it needs to be required. And then use clearErrors() where needed.

With your solution above, theer is more code/processing and then I also have to manually define the attribute as ‘required’.

Also I found that if I want to validate multiple attributes on a custom function, each attribute will get validated multiple times! I.e 3 attributes = 3 x validation for each attribute.

You can use afterValidate as well, there are no difference.

My suggestion was just because I consider more polite use the ‘standard instrument’ like rules instead of override the framework function, but is a pure question of style, no difference at all.

If you get validate many time is because you set the rule for more 3 attributes, so the rule will be applied to all attributes, 3 times. You have 2 options: create an attribute depending validate function or create a single function for all field to validate (function to be called once, so only one attribute should figure in rules).

Attribute depending is like that:




public function carCheck($attribute)

{

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

   {

        $validator=new CRequiredValidator;

	$validator->attributes=array($attribute);

	$validator->validate($this);

   }      

}



Like that carCheck is a ‘conditional required’, you can add as parameters you want and they will be checked if vehicle car is selected. You will add all attributes in rule like all standard rules.

Note that if more than one vehicle type is selected and all this vehicle type require an attribute, them the attribute will validated more than one time.

If you are in this situation is better to write a single function for all ‘conditional required’ field, create an array of field to validate and validate them all one time.