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.