A Couple Questions About Dynamic Content And Ajax

I want to show some dynamic content to the user, so use a dropdown like so:


<?php 

$form->dropdownListRow($model, 'type', array_merge(array(''=>'---'),ProfileFieldRule::itemAlias('type')) ,

                array('class' => 'span5', 'maxlength' => 45, 

                    'ajax'=>array(

                        'type'=>'POST',

                        'url'=>CController::createUrl('ProfileFieldRule/showAttributes'),

                        'update'=>'#showAttributes',                        

                        ))));

?>

the action showAttributes renders some form elements, stuff like:


echo CHtml::activeDropDownList($attr, $name, array('true', 'false'));

Question 1 At this point, I’d like to know if there’s a way I can access the $form variable that was created in the view? It’s easier, than CHtml and if I make changes they’d persist I wouldn’t have to come update them again here. If not, that’s ok, I can work with CHtml.

So far so good, until the form is submitted with errors. At that point, the generated content disappears until you change the dropdown again.

Question 2 What’s the best way to make it persist? At present I check if it’s set and copy the code from showAttributes. This seems pretty redundant.

Question 3 How would I go about adding ajax validation to this form? The non dynamic portion is simple, but I don’t know how to add the dynamic attributes to what’s being validated.

Thanks in advance for any help, hopefully I’ve made it more clear with this edit!

Dear cnorris

Your questions just reflected my thoughts when I was closely following this thread.

Ajax Form Content Validation Validating form content filled by ajax request

I hope we have answers for the first two questions and possible for third one also.

Here we have simple scenario.

Model:Profile

Attributes:name,age,married,spouseName,SpouseAge.

When creating a profile one has to enter his name and age.

There is a dropDownList to choose married status.

If one chooses a married status as ‘married’

then we have ajax load the form elements spouseName and spouseAge.

Profile.php




class Profile extends CActiveRecord

{

.............................................................

public function rules()

	{

		return array(

			array('name, age', 'required'),

			array('spouseName','spouseValidate'),

			array('spouseAge','spouseValidate'),

			array('age, married, spouseAge', 'numerical', 'integerOnly'=>true),

			array('name, spouseName', 'length', 'max'=>64),

			array('id, name, age, married, spouseName, spouseAge', 'safe', 'on'=>'search'),

		);

	}

    

    public function spouseValidate($attribute,$params)

    {

		if($this->married==1 && $this->$attribute=='')

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

		

    }

.....................................................................................

}



We are enabling ajax validation since , since clientside validation is not possible for customvalidator methods.

We are invoking error methods for ajax loaded attributes without echoing them. This ensures that

yiiActiveForm registers necessary scripts for those attributes beforehand.

_form.php




<div class="form">


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

	'id'=>'profile-form',

	'enableAjaxValidation'=>true,

	//'enableClientValidation'=>true,

	'clientOptions'=>array(

	    "validateOnSubmit"=>true, //This is very important to ensure that ajax loaded elements would perist 

                                      //after submitting the form if there are errors in the form.

	),

)); ?>


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


	<?php echo $form->errorSummary($model); ?>


<!-- We have here invoking the CActiveForm::error for ajax loaded attribute fields without echoing.-->

        <?php  $form->error($model,'spouseName'); ?>

        <?php  $form->error($model,'spouseAge'); ?>	


	<div class="row">

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

		<?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>64)); ?>

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

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'age'); ?>

		<?php echo $form->textField($model,'age'); ?>

		<?php echo $form->error($model,'age'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'married'); ?>

		<?php echo $form->dropDownList($model,'married',array(0=>"Unmarried",1=>"Married"),array(

		"ajax"=>array(

		    "url"=>CHtml::normalizeUrl(array('profile/spouse')),

		    'type'=>"POST",

		    "update"=>"#spouse"

		),'separator'=>""

		)); ?>

		<?php echo $form->error($model,'married'); ?>

	</div>


	<div id="spouse"></div>


	<div class="row buttons">

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

	</div>


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


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




Controller




public function actionCreate()

	{

		$model=new Profile;

		$this->performAjaxValidation($model);

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

		{

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

			if($model->save())

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

		}


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

			'model'=>$model,

		));

	}


public function actionSpouse()

	{   $model=new Profile;

	    if(isset($_POST['Profile']['married']) && $_POST['Profile']['married']==1)

		   $this->renderPartial("_spouse",array('model'=>$model),true,true);

	    echo $this->clips['spouse'];

	}



_spouse.php is actually the original form that was generated by GII.

We are going to cut the necessary elements by CController:clip.

_spouse.php




<div class="form">


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

	'id'=>'profile-form',

	'enableAjaxValidation'=>true,

	//'enableClientValidation'=>true,

	'clientOptions'=>array(

	    "validateOnSubmit"=>true,)

	

)); ?>


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


	<?php echo $form->errorSummary($model); ?>


	<div class="row">

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

		<?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>64)); ?>

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

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'age'); ?>

		<?php echo $form->textField($model,'age'); ?>

		<?php echo $form->error($model,'age'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'married'); ?>

		<?php echo $form->textField($model,'married'); ?>

		<?php echo $form->error($model,'married'); ?>

	</div>


<!--We are going to clip the spouse elements here -->


<?php $this->beginClip("spouse");?>

	<div class="row">

		<?php echo $form->labelEx($model,'spouseName'); ?>

		<?php echo $form->textField($model,'spouseName',array('size'=>60,'maxlength'=>64)); ?>

		<?php echo $form->error($model,'spouseName'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'spouseAge'); ?>

		<?php echo $form->textField($model,'spouseAge'); ?>

		<?php echo $form->error($model,'spouseAge'); ?>

	</div>

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


	<div class="row buttons">

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

	</div>


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


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



Now every thing is fine. We are able to validate the ajax loaded elements.

There is one aberration.

To validate a particular field on ajax loaded part of the form, we have to click the static form on the top

and then we have to come back to the ajax loaded part.

This is annoying.

This is because events registered for form fields are not delegated to ajax loaded parts.

This is obvious by looking into yiiActiveForm.js.

yiiActiveForm.js




<!--Around line 111------------------------------------------->

..............

$.each(settings.attributes, function (i, attribute) {

				if (this.validateOnChange) {

					$form.find('#' + this.inputID).change(function () {

						validate(attribute, false);

					}).blur(function () {

						if (attribute.status !== 2 && attribute.status !== 3) {

							validate(attribute, !attribute.status);

						}

					});

				}

..............

..............



This should be changed to make the events live to bind with newly loaded form elements.





...................

...................

$.each(settings.attributes, function (i, attribute) {

				if (this.validateOnChange) {

					$form.find('#' + this.inputID).live("change",function () {

						validate(attribute, false);

					}).live("blur",function () {

						if (attribute.status !== 2 && attribute.status !== 3) {

							validate(attribute, !attribute.status);

						}

					});

...................

...................



Now everything is perfect.

But one painful thing is we have to alter something in the core.I mean yiiActiveForm.js.

That is not acceptable.

I hope some descent workaround is possible for that.

Regards.

I’d like to say thanks for this well thought out, extremely, extremely helpful reply. This solves all the issues I was having, including the ones I didn’t articulate in my post. Not only did this:

[i]

We are invoking error methods for ajax loaded attributes without echoing them. This ensures that

yiiActiveForm registers necessary scripts for those attributes beforehand.

[/i]

open up a world of possibilities and time saving for me, but also this:


	'clientOptions'=>array(

	    "validateOnSubmit"=>true, //This is very important to ensure that ajax loaded elements would perist 

                                      //after submitting the form if there are errors in the form.

and not to mention the ‘clipse’ function, which to be honest, I didn’t know existed. I will do a better job researching before posting a question again :confused:

I asked the same question on stack overflow and ended up editing the framework so that it would behave like this (based on the recommendation I received):


$.each(settings.attributes, function (i, attribute) {

				if (this.validateOnChange) {

					$form.find('#' + this.inputID).live("change",function () {

						validate(attribute, false);

					}).live("blur",function () {

						if (attribute.status !== 2 && attribute.status !== 3) {

							validate(attribute, !attribute.status);

						}

					});

If you’ve not done so, I’d suspect this example would server as a good tutorial (though I might be the only one running into these troubles).

Thanks again,

cnorris