Working with ActiveForm via JavaScript
PHP side of ActiveForm, which is usually more than enough for majority of projects, is described well in the official Yii 2.0 guide. It is getting a bit more tricky when it comes to advanced things such as adding or removing form fields dynamically or triggering individual field validation using unusual conditions.
In this recipe you'll be introduced to ActiveForm JavaScript API.
Preparations
We're going to use basic project template contact form for trying things out so install it first.
Triggering validation for individual form fields
$('#contact-form').yiiActiveForm('validateAttribute', 'contactform-name');
Trigger validation for the whole form
$('#contact-form').yiiActiveForm('validate', true);
The second passed argument true
forces validation of the whole form.
Using events
$('#contact-form').on('beforeSubmit', function (e) {
if (!confirm("Everything is correct. Submit?")) {
return false;
}
return true;
});
Available events are:
beforeValidate
.afterValidate
.beforeValidateAttribute
.afterValidateAttribute
.beforeSubmit
.ajaxBeforeSend
.ajaxComplete
.
Adding and removing fields dynamically
To add a field to validation list:
$('#contact-form').yiiActiveForm('add', {
id: 'address',
name: 'address',
container: '.field-address',
input: '#address',
error: '.help-block',
validate: function (attribute, value, messages, deferred, $form) {
yii.validation.required(value, messages, {message: "Validation Message Here"});
}
});
To remove a field so it's not validated:
$('#contact-form').yiiActiveForm('remove', 'address');
Updating error of a single attribute
In order to add error to the attribute:
$('#contact-form').yiiActiveForm('updateAttribute', 'contactform-subject', ["I have an error..."]);
In order to remove it:
$('#contact-form').yiiActiveForm('updateAttribute', 'contactform-subject', '');
Update error messages and, optionally, summary
$('#contact-form').yiiActiveForm('updateMessages', {
'contactform-subject': ['Really?'],
'contactform-email': ['I don\'t like it!']
}, true);
The last argument in the above code indicates if we need to update summary.
Listening for attribute changes
To attach events to attribute changes like Select, Radio Buttons, etc.. you can use the following code
$("#attribute-id").on('change.yii',function(){
//your code here
});
Getting Attribute Value
In order to be compatible with third party widgets like (Kartik), the best option to retrieve the actual value of an attribute is:
$('#form_id').yiiActiveForm('find', 'attribute').value
Custom Validation
In case you want to change the validation of an attribute in JS based on a new condition, you can do it with the rule property whenClient, but in the case you need a validation that doesn't depends on rules (only client side), you can try this:
$('#form_id').on('beforeValidate', function (e) {
$('#form_id').yiiActiveForm('find', 'attribute').validate = function (attribute, value, messages, deferred, $form) {
//Custom Validation
}
return true;
});
AJAX form submission with server validation
To submit form via AJAX it is required to attach a handler on beforeSubmit
event. Handler will replace default form submission
and is responsible for sending data to the server and displaying error messages if server validation fails. Displaying validation
messages requires support at the controller side.
Attach a handler to form:
$('#contact-form').on('beforeSubmit', function () {
var $yiiform = $(this);
$.ajax({
type: $yiiform.attr('method'),
url: $yiiform.attr('action'),
data: $yiiform.serializeArray(),
}
)
.done(function(data) {
if(data.success) {
// data is saved
} else if (data.validation) {
// server validation failed
$yiiform.yiiActiveForm('updateMessages', data.validation, true); // renders validation messages at appropriate places
} else {
// incorrect server response
}
})
.fail(function () {
// request failed
})
return false; // prevent default form submission
})
Controller support:
public function actionUpdate($id)
{
$model = $this->findModel($id);
if (Yii::$app->request->isAjax) {
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->asJson(['success' => true]);
}
$result = [];
// The code below comes from ActiveForm::validate(). We do not need to validate the model
// again, as it was already validated by save(). Just collect the messages.
foreach ($model->getErrors() as $attribute => $errors) {
$result[yii\helpers\Html::getInputId($model, $attribute)] = $errors;
}
return $this->asJson(['validation' => $result]);
}
return $this->render('update', [
'model' => $model,
]);
}