3 Models - One Dynamic Form And Ajax Validation

Hello everybody,

Warning: This is a very long post.

This problem is driving me crazy…

I dont know if im too stupid or this isimpossible?

I read tons of threads about this topic,

but never got this working for me…

I would prefer a solution without any extensions,

because this is acutally an example just for my personal learning purpose…

And I’m afraid that some extensions maybe are to complex for me to understand everything.

Okay, now to my problem:

I want to create a "mote complex" dynamic form with 3 models.

Two of the models are tabular & dynamic.

And I can not get the ajax validation working like I want it to have.

(Validation without ajax is no problem)

Lets take the classroom / student thing for example.

So I have following Models in my form…

  1. FClass

    • id

    • name

  2. FTeacher

    • id

    • class_id

    • teacher_id

  3. FStudent

    • id

    • class_id

    • firstname

    • lastname

    • gender

(The Models all have an F in front, because I also try to build this as module…)

To keep it simple as possible:

  • Every class can have multiple teachers AND multiple students.

  • Every teacher can have several classes.

  • Every student can only have one class.

So there are buttons to dynamically add or remove teachers AND students via jquery.

===========================

WORKING SOLITION (WITHOUT AJAX):

_FORM:

[spoiler]




<?php

// register jquery

Yii::app()->clientScript->registerCoreScript('jquery');

?>




<div class="form">

        

<?php 

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

	'id'                        => 'classroom-form',

)); 

	

// display error summary 

echo $form->errorSummary(array_merge(array($class), $teachers, $students));

?> 


    

<fieldset><legend>Class Information</legend>


	<!-- CLASS NAME -->

	<div class="row" id="FClass_name_id_row">

		<?php echo $form->labelEx	($class,'name'); ?>

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

		<?php echo $form->error		($class,'name'); ?>

	</div>




	<!-- TEACHERS -->

	<div id="teacher_adder_div">

		<?php 

		$num_t = count($teachers); 


		for($x=0; $x < $num_t; $x++){

			echo '<div class="row" id="FTeacher_'.$x.'_user_id_row">';

			echo $form->labelEx         ($teachers[$x],"[$x]user_id");

			echo $form->dropDownList    ($teachers[$x],"[$x]user_id", $dropdown_teachers, array('empty' => 'Select')); 

			echo $form->error           ($teachers[$x],"[$x]user_id"); 

			echo '</div>';

		}

		?>

	</div>


	<input type="button" id="add-teacher" value="add" />

	<input type="button" id="rem-teacher" value="remove" />

   

	<?PHP 

	// render partial as json object

	$newTeacherRow = CJSON::encode(

		$this->renderPartial(

			'_formTeacherRow', 

			array(

				'teacher'=>new FTeacher, 

				'list' => $dropdown_teachers, 

				'form' => $form

				), 

			true, 

			false

		)

	);




	// jQuery function to add and remove new teachers

	Yii::app()->clientScript->registerScript(

		'manageTeachers',

		'

		   var nr='.$num_t.';


		   // add a new row to div

		   $("#add-teacher").click(

			   function(){

				   // get div where to add new elements

				   var adder=$("#teacher_adder_div");

				   

				   // create new div

				   var newRow = '.$newTeacherRow.';                     

				   

				   // replace string "JQUERY_REPLACE_ID" with current id

				   var newRow = newRow.replace(/JQUERY_REPLACE_ID/g, nr);

				   

				   // Append Elements to new row-div

				   adder.append(newRow.toString());

				   

				   // count elements

				   nr=nr+1;

			   }

		   );

		   

		   // remove row from div

		   $("#rem-teacher").click(

			   function(){

				   var oldDiv = $("#teacher_adder_div").children("div").eq(nr-1);

				   oldDiv.remove();

				   

				   if(nr > 0){

					   nr=nr-1;

				   }else{

					   nr=0;

				   }

			   }

		   );

		'

	);


	?>




</fieldset>




<!-- ############################################################### STUDENTS -->

<fieldset>

	<legend>Students</legend>

	

	<table>

		<thead>

			<tr>

				<th>Firstname</th>

				<th>Lastname</th>

				<th>Gender</th>

			</tr>

		</thead>

		

		<tbody id="student_adder">

			<!-- ADD STUDENTS DYNAMICALLY -->

			<?php 

			$num_studs = count($students);


			for($d=0; $d < $num_studs; $d++){

				echo '<tr id="FStudent_'.$d.'_row">';

					echo '<td>';

						echo $form->textField($students[$d],"[$d]firstname",array('size'=>14,'maxlength'=>14)); 

						echo $form->error($students[$d],"[$d]firstname"); 

					echo '</td>';

					echo '<td>';

						echo $form->textField($students[$d],"[$d]lastname",array('size'=>14,'maxlength'=>14));

						echo $form->error($students[$d],"[$d]lastname"); 

					echo '</td>';

					echo '<td>';

						echo $form->dropDownList($students[$d],"[$d]gender", FStudent::getGenderTypes(), array('empty' => 'Select'));

						echo $form->error($students[$d],"[$d]gender"); 

					echo '</td>';

				echo '</tr>';

			}

			?>

		</tbody>

	</table>

	

	

	<input type="button" id="add-student-row" value="add" />

	<input type="button" id="rem-student-row" value="remove" />

	

	<?PHP 

	// render partial as json object

	$newStudentRow = CJSON::encode($this->renderPartial('_formStudentRow', array('student'=>new FStudent, 'form'=>$form), true));

	

	// jQuery function to add and remove new copyreceivers

	Yii::app()->clientScript->registerScript(

		'manageStudentRows',

		'

		   var num='.$num_studs.';

		   

		   // add a new row to div

		   $("#add-student-row").click(

			   function(){

				   // get div where to add new elements

				   var adder=$("#student_adder");

				   

				   // create new div

				   var newRow = '.$newStudentRow.';

										  

				   // replace string "JQUERY_REPLACE_ID" with current id

				   newRow = newRow.replace(/JQUERY_REPLACE_ID/g, num)


				   // Append Elements to new row-div

				   adder.append(newRow);


				   // count elements

				   num=num+1;


			   }

		   );

		   

		   

		   // remove row from div

		   $("#rem-student-row").click(

			   function(){

				   var oldDiv = $("#student_adder").children("tr").eq(num-1);

				   oldDiv.remove();

				   if(num > 0){

					   num=num-1;

				   }else{

					   num=0;

				   }

			   }

		   );

		'

	);

	?>


</fieldset>


<div class="row buttons">

	<?php echo CHtml::submitButton($class->isNewRecord ? 'Save' : 'Update'); ?>

</div>




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


</div><!-- form -->



[/spoiler]

AJAX ADDED PARTIALS:

_formTeacherRow:

[spoiler]




<div class="row" id="FTeacher_JQUERY_REPLACE_ID_user_id_row">

    <?php echo $form->labelEx         ($teacher,"[JQUERY_REPLACE_ID]user_id"); ?>

    <?php echo $form->dropDownList    ($teacher,"[JQUERY_REPLACE_ID]user_id", $list, array('empty' => 'Select')); ?>

    <?php echo $form->error           ($teacher,'[JQUERY_REPLACE_ID]user_id'); ?>

</div>



[/spoiler]

_formStudentRow:

[spoiler]




<tr id="FStudent_[JQUERY_REPLACE_ID]_row">


    <td>

        <?php echo $form->textField       ($student,'[JQUERY_REPLACE_ID]firstname',array('size'=>14,'maxlength'=>14)); ?> 

        <?php echo $form->error           ($student,'[JQUERY_REPLACE_ID]firstname'); ?>

    </td>

    <td>

        <?php echo $form->textField       ($student,'[JQUERY_REPLACE_ID]lastname',array('size'=>14,'maxlength'=>14)); ?> 

        <?php echo $form->error           ($student,'[JQUERY_REPLACE_ID]lastname'); ?>

    </td>

    <td>

        <?php echo $form->dropDownList($student,"[JQUERY_REPLACE_ID]gender", FStudent::getGenderTypes(), array('empty' => 'select')); ?>

    </td>

</tr>



[/spoiler]

CONTROLLER:

[spoiler]




<?


public function actionCreate(){

			

	// get dropdown list

	$dropdown_teachers	 = FTeacher::model()->getDropdownUser();

	

	// create form objects

	$class		= new FClass;

	$teachers	= array(new FTeacher); 

	$students	= array(new FStudent); 

	

	

	// if everything is posted 

	if(

		isset($_POST['FClass']) && 

		isset($_POST['FTeacher']) &&

		isset($_POST['FStudent'])

	){

		$valid=true;

		

		// collect and validate teachers (tabular) 

		$numTeacher = count($_POST['FTeacher']);

		

		for($x=0; $x < $numTeacher; $x++){

			if(isset($_POST['FTeacher'][$x])){

				$teachers[$x] = new FTeacher; 

				$teachers[$x]->attributes  = $_POST['FTeacher'][$x];

				$valid=$teachers[$x]->validate() && $valid;

			}

		}  

		

		// collect and validate student (tabular)

		$numStuds = count($_POST['FStudent']);

		

		for($x=0; $x < $numStuds; $x++){

			if(isset($_POST['FStudent'][$x])){

				$students[$x] = new FStudent; 

				$students[$x]->attributes  = $_POST['FStudent'][$x];

				$valid=$students[$x]->validate() && $valid; 

			}

		} 

		

		// collect and validate class (single)

		$class->attributes = $_POST['FClass'];

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

		

		// all data posted and successfull validated 

		if($valid){

			

			// just give out all the post data ... 

			// saving issue comes, when i solved my validation problem.

			echo "<pre>";

			print_r($_POST);

			echo "<pre>";

			

		}

	}

	

 

	// default => render form (exept everythng was validated)

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

		'class'				=>$class,

		'dropdown_teachers'	=>$dropdown_teachers,

		'teachers'			=>$teachers,

		'students'			=>$students

	));

	

}


?>



[/spoiler]

Okay - and now I would prefer to add ajaxValidation to this…

So I changed the following.

In the form:

[spoiler]




<?

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

        'id'                        => 'classroom-form',

        'enableAjaxValidation'      =>  true,

        'clientOptions'             =>  array (

			'validateOnChange'		=> 	false,

			'validateOnType'		=>	false,

			'validateOnSubmit'      =>  true,    

        ) 

    )); 

?>



[/spoiler]

In the Controller:

[spoiler]




<?

	...

		$class		= new FClass;

		$teachers	= array(new FTeachers); 

		$students	= array(new FStudents); 

        

		$this->performAjaxValidation($class, $teachers, $students);

	...

?>



[/spoiler]

And wrote the performAjaxValidation function:

[spoiler]




<?

    protected function performAjaxValidation($class, $teachers, $students)

    {

        if(isset($_POST['ajax']) && $_POST['ajax']==='classroom-form'){

        	

			$class 		= CJSON::decode(CActiveForm::validate($class));

			$teachers 	= CJSON::decode(CActiveForm::validateTabular($teachers));

			$students 	= CJSON::decode(CActiveForm::validateTabular($students));

			

			echo CJSON::encode(CMap::mergeArray($class, $teachers, $studends));

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

        }

    }

?>



[/spoiler]

Now follwing happends:

  1. I leave the form blank.

  2. In firebug I can see that the form ist posted.

  3. I receive an JSON Object with all errors for all models / rows back.

  4. ErrorSuammary / On field error displays the errors for the "class" model only.

  5. The error summary AND onfield error does not display the errors for "teachers" and "students"…

This really drives me crazy. :(

What am I doing wrong / missing here?

Could somebody give me a hint in the right direction or show me an example?

Is there any "easy to understand" solution for this problem wich does not require any extensions?

… and further:

How could I solve it, when I want to add rows dynamically also with onChange enabled?

Hope this was not tooooo long for you.

Every help appriciated!

Best Regards & Thanks in advance

Edit: Spoilered the code to make this thread easier to read.

may be this can help http://www.yiiframework.com/wiki/362/how-to-use-multiple-instances-of-the-same-model-in-the-same-form/