Please help on handling validation on repeatable fields

Hi guys,

I’m having some trouble with a form that handles two modals’ inputs in one. The second modal (ExerciseRoutine) has repeatable fields and I’m having trouble working out how to validate those fields.

If you take a look at the screenshot of the form attached, you will get a better idea on what I’m trying to achieve.

The actionCreate method concerned is:




	public function actionCreate()

	{

	    $routine = new Routine;

	    

	    if(isset($_POST['Routine'], $_POST['RoutineExercise']))

	    {

	        // Populate input data to routine modal

	        $routine->attributes=$_POST['Routine'];

	        // Validate routine inputs

	        $valid = $routine->validate();

	        

	        foreach($_POST['RoutineExercise'] as $i=>$item)

	        {

	        	$routineExercise[$i] = new RoutineExercise;

				$routineExercise[$i]->attributes=$_POST['RoutineExercise'][$i];

				$valid = $routineExercise[$i]->validate() && $valid;

			}

			 

			// Check to see if both modal's inputs are valid

			if($valid) 

			{  

			

				// Save routine modal

				if($routine->save(false)) 

				{    

	                

					foreach($_POST['RoutineExercise'] as $i=>$item)

					{

					

						$routineExercise = new RoutineExercise;

						

					    $routineExercise->routine_id = $routine->id; 

					    

					    // Set modal attributes

					    $routineExercise->exercise_id = $_POST['RoutineExercise'][$i]['exercise_id'];

					    $routineExercise->sets = $_POST['RoutineExercise'][$i]['sets'];

					    $routineExercise->save(false); // Save routineExercise modal

			    	}

			    	

			    }

			    	

					Yii::app()->user->setFlash('success', '<strong>Routine created!</strong> You have successfully created this routine.');

					$this->redirect(array('view','id'=>$routine->id));

			}	

	    }

	    	 

	    $routineExercise = new RoutineExercise;	 

	    	 

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

	        'routine'=>$routine,

	        'routineExercise'=>$routineExercise,

	    ));

	}



The validation works fine for the Routine modal, but not for the RoutineExercise repeatable fields, if their input is invalid the modal is not saved (as it does in fact fail validation), but no errors are shown on the form.

The _form view file contains:




					<?php $form = $this->beginWidget('bootstrap.widgets.BootActiveForm', array(

					    'id'=>'horizontalForm',

					    'type'=>'horizontal',

					    'htmlOptions'=>array('class'=>'well'),

					)); ?>

					    <?php echo $form->textFieldRow($routine, 'name', array('hint'=>'This is what your routine will be called.')); ?>

					    <hr />

					    <div id="input1" class="routine_exercise">

						<?php echo $form->dropDownListRow($routineExercise, '[1]exercise_id', CHtml::listData(Exercise::model()->findAll(array(

						'condition'=>'(user_id IS NULL) OR user_id='.Yii::app()->user->id, 

						'order'=>'target_muscle')), 

						'id', 'name', 'target_muscle'), 

						array('prompt'=>'Select exercise…')); ?>

						 <?php echo $form->textFieldRow($routineExercise, '[1]sets', array('hint'=>'The number of sets for this exercise. You will define each set\'s target reps on the next page.')); ?>

    					</div>

						<input type="button" id="btnAdd" class="btn btn-success btn-mini" value='Add Exercise' />

						<input type="button" id="btnDel" class="btn btn-danger btn-mini" value='Remove Exercise' />

						<div class="form-actions">

						    <?php echo CHtml::htmlButton($routine->isNewRecord ? '<i class="icon-ok"></i> Create' : '<i class="icon-ok"></i> Save', array('class'=>'btn btn-primary', 'type'=>'submit')); ?>

						</div>

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



Any help with this would be very much appreciated!

Thanks,

Aaron

Hm, yeah. You need a $form->error() for each of your RoutineExercise models. Otherwise, there’s no way to display errors for them in the form. Also: You are dynamicaly adding RoutineExercise’s, but you’re only displaying the very first one.

Yea there is no $form->error() because I am using the bootstrap extension which handles errors, but perhaps this is part of the problem. I use jQuery to allow the user to add/remove the extra fields, but yea if the form fails validation, it will only show the last one entered.

Any ideas?

Thanks

Well, you want multiple instances of RoutineExercise. But your entire logic only handles one. You’re iterating over $_POST[‘RoutineExercise’], but you’re always saving to the same $routineExercise. Turn that into an array, and you should be fine ;)

I thought I had set that as an array on line 14:


$routineExercise[$i] = new RoutineExercise;

What more should I add?

Thank you very much for your help so far; I think once I work all this out I will write a Wiki article on how to handle repeatable fields etc, as there isn’t much help on it currently!

Ah, I fell for the crooked logic in your create action. Typed freehand, no guarantee on nothing:




public function actionCreate()

{

  $routine = new Routine;

  $exercises=array();


  if(isset($_POST['Routine'], $_POST['RoutineExercise']))

  {

    $routine->attributes=$_POST['Routine'];

    $valid = $routine->validate();


    foreach($_POST['RoutineExercise'] as $item)

    {

      $routineExercise=new RoutineExercise;

      $routineExercise->attributes=$item;

      $valid=$routineExercise->validate() && $valid;

      $exercises[]=$routineExercise;

    }


    if($valid) 

    {  

      $routine->save(false); 

      foreach($exercises as $exercise)

      {

        $exercise->routine_id = $routine->id; 

        $exercise->save(false); // Save routineExercise modal

      }

    }

                                

    Yii::app()->user->setFlash('success', '<strong>Routine created!</strong> You have successfully created this routine.');

    $this->redirect(array('view','id'=>$routine->id));

  }


  $exercises = array(new RoutineExercise);

                 

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

    'routine'=>$routine,

    'routineExercise'=>$exercises,

  ));

}

far from perfect, so not glitch-free, but it should get you started.

Thank you for that Da:Sourcerer, that’s really helped me to understand!

The problem is I now get this PHP error:

Fatal error: Call to a member function hasErrors() on a non-object in /Applications/XAMPP/xamppfiles/htdocs/dmp/protected/extensions/bootstrap/widgets/input/BootInput.php on line 243

With the offending line in BootInput.php being:


if ($this->model->hasErrors($this->attribute))

I’m guessing this means that the problem now lies with the extension I’m using (bootstrap) not being able to handle the attribute input as an array?

Thank you once again!

I really can’t tell. I’ve never used that extension :(

Hm, but it could be that you haven’t modified your view: Iterate over routineExercise.

Ok so I have removed that widget to create the form and I have instead created the form using CActiveForm. So now my _form view file looks like:




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

						'id'=>'routine-exercise-set-form',

						'enableAjaxValidation'=>false,

						'htmlOptions'=>array('class'=>'form-horizontal well'),

					)); ?>

						<div class="control-group<?php if($form->error($routine, 'name')) echo ' error'; ?>">

							<?php echo $form->labelEx($routine,'name', array('class'=>'control-label')); ?>

							<div class="controls">

								<?php echo $form->textField($routine, 'name'); ?>

								<?php if($form->error($routine, 'name')):?>

								<span class="help-inline"><?php echo $form->error($routine, 'name'); ?></span>

								<?php endif; ?>

								<p class="help-block">This is what your routine will be called.</p>

							</div>

						</div>

						

					    <hr />

					    <div id="input1" class="routine_exercise">

					    	<div class="control-group<?php if($form->error($routineExercise,'[1]exercise_id')) echo ' error'; ?>">

						    	<?php echo $form->labelEx($routineExercise,'[1]exercise_id', array('class'=>'control-label')); ?>

						    	<div class="controls">

									<?php echo $form->dropDownList($routineExercise, '[1]exercise_id', CHtml::listData(Exercise::model()->findAll(array(

									'condition'=>'(user_id IS NULL) OR user_id='.Yii::app()->user->id, 

									'order'=>'target_muscle')), 

									'id', 'name', 'target_muscle'), 

									array('prompt'=>'Select exercise…')); ?>

									<?php if($form->error($routineExercise,'[1]exercise_id')):?>

									<span class="help-inline"><?php echo $form->error($routineExercise,'[1]exercise_id'); ?></span>

									<?php endif; ?>

								</div>

							</div>

							

							

							<div class="control-group<?php if($form->error($routineExercise,'[1]sets')) echo ' error'; ?>">

								<?php echo $form->labelEx($routineExercise,'[1]sets', array('class'=>'control-label')); ?>

								<div class="controls">

									<?php echo $form->textField($routineExercise,'[1]sets'); ?>

									<?php if($form->error($routineExercise,'[1]sets')):?>

									<span class="help-inline"><?php echo $form->error($routineExercise,'[1]sets'); ?></span>

									<?php endif; ?>

									<p class="help-block">The number of sets for this exercise. You will define each set's target reps on the next page.</p>

								</div>

							</div>

							

    					</div>

						<input type="button" id="btnAdd" class="btn btn-success btn-mini" value='Add Exercise' />

						<input type="button" id="btnDel" class="btn btn-danger btn-mini" value='Remove Exercise' />

					

						<div class="form-actions">

						    <?php echo CHtml::submitButton($routine->isNewRecord ? 'Create' : 'Save', array('class'=>'btn btn-primary')); ?>

						</div>

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



This now leads to me getting the PHP error: get_class() expects parameter 1 to be object, array given

Does this help narrow it down?

in my controller

public function actionCreate()

    {


            &#036;model=new Users;


            &#036;model2=new Authassignment;


            // Uncomment the following line if AJAX validation is needed


            // &#036;this-&gt;performAjaxValidation(&#036;model);





            if(isset(&#036;_POST['Users'],&#036;_POST['Authassignment']))


            {


                    //&#036;model-&gt;attributes=&#036;_POST['Users'];


                    //&#036;model2-&gt;attributes=&#036;_POST['Authassignment'];

// populate input data to $model and $model2

                    &#036;model-&gt;attributes=&#036;_POST['Users'];


                   // &#036;model2-&gt;attributes=&#036;_POST['Users'];


                    


                    // validate BOTH &#036;model and &#036;model2


                    &#036;valid=&#036;model-&gt;validate();


                    &#036;valid=&#036;model2-&gt;validate() &amp;&amp; &#036;valid;


                    


                    if(&#036;valid)


                    {


                            // use false parameter to disable validation


                            &#036;model-&gt;save(false);


							//&#036;model2-&gt;save(false);


                            // ...redirect to another page


                    }


					 if(&#036;model-&gt;save())


                            &#036;this-&gt;redirect(array('view','id'=&gt;&#036;model-&gt;id));


            }





            &#036;this-&gt;render('create',array(


                    'model'=&gt;&#036;model,


                    'model2'=&gt;&#036;model2,


            ));


    }

in my form

echo $form->dropdownlist($model2,‘itemname’,array(‘admin’=>‘admin’,‘2’=>‘2’,‘3’=>‘3’,‘4’=>‘4’)),"</td>";

i got error

Call to a member function hasErrors() on a non-object in D:\pandi_xampp\xampp\xampp\htdocs\yii_framework\framework\web\helpers\CHtml.php on line 1405

[b]

[/b]

please help me any one

thanks

[b]

[/b]