Merge branch 'deferred-validation' of github.com:Alex-Code/yii2 into Alex-Code-deferred-validation

Conflicts:
	framework/CHANGELOG.md
This commit is contained in:
Qiang Xue
2014-07-31 11:28:27 -04:00
4 changed files with 125 additions and 51 deletions

View File

@@ -487,6 +487,7 @@ predefined variables:
- `attribute`: the name of the attribute being validated. - `attribute`: the name of the attribute being validated.
- `value`: the value being validated. - `value`: the value being validated.
- `messages`: an array used to hold the validation error messages for the attribute. - `messages`: an array used to hold the validation error messages for the attribute.
- `deferred`: an array which deferred objects can be pushed into.
In the following example, we create a `StatusValidator` which validates if an input is a valid status input In the following example, we create a `StatusValidator` which validates if an input is a valid status input
against the existing status data. The validator supports both server side and client side validation. against the existing status data. The validator supports both server side and client side validation.
@@ -535,6 +536,57 @@ JS;
] ]
``` ```
### Deferred validation
If you need to perform any asynchronous validation you can use a [deferred object](http://api.jquery.com/category/deferred-object/).
deferred objects must be pushed to the ```deferred``` array for validation to use them.
Once any asynchronous validation has finished you must call ```resolve()``` on the Deferred object for it to complete.
This example shows reading an image to check the dimensions client side (```file``` will be from an input of type=file).
```php
...
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
var def = $.Deferred();
var img = new Image();
img.onload = function() {
if (this.width > 150) {
messages.push('Image too wide!!');
}
def.resolve();
}
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
}
reader.readAsDataURL(file);
deferred.push(def);
JS;
}
...
```
Ajax can also be used and pushed straight into the deferred array.
```
deferred.push($.get("/check", {value: value}).done(function(data) {
if ('' !== data) {
messages.push(data);
}
}));
```
The ```deferred``` array also has a shortcut method ```add```.
```
deferred.add(function(def) {
//Asynchronous Validation here
//The context of this function and the first argument is the Deferred object where resolve can be called.
});
```
> Note: `resolve` must be called on any deferred objects after the attribute has been validated or the main form validation will not complete.
### Ajax validation ### Ajax validation
Some kind of validation can only be done on server side because only the server has the necessary information Some kind of validation can only be done on server side because only the server has the necessary information

View File

@@ -165,6 +165,7 @@ Yii Framework 2 Change Log
- Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) - Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs)
- Enh #4360: Added client validation support for file validator (Skysplit) - Enh #4360: Added client validation support for file validator (Skysplit)
- Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) - Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma)
- Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code)
- Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp) - Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)

View File

@@ -271,6 +271,19 @@
}, data.settings.validationDelay); }, data.settings.validationDelay);
}; };
/**
* Returns an array prototype with a shortcut method for adding a new deferred.
* The context of the callback will be the deferred object so it can be resolved like ```this.resolve()```
* @returns Array
*/
var deferredArray = function () {
var array = [];
array.add = function(callback) {
this.push(new $.Deferred(callback));
};
return array;
};
/** /**
* Performs validation. * Performs validation.
* @param $form jQuery the jquery representation of the form * @param $form jQuery the jquery representation of the form
@@ -280,24 +293,31 @@
var validate = function ($form, successCallback, errorCallback) { var validate = function ($form, successCallback, errorCallback) {
var data = $form.data('yiiActiveForm'), var data = $form.data('yiiActiveForm'),
needAjaxValidation = false, needAjaxValidation = false,
messages = {}; messages = {},
deferreds = deferredArray();
$.each(data.attributes, function () { $.each(data.attributes, function () {
if (data.submitting || this.status === 2 || this.status === 3) { if (data.submitting || this.status === 2 || this.status === 3) {
var msg = []; var msg = [];
messages[this.id] = msg;
if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) { if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) {
if (this.validate) { if (this.validate) {
this.validate(this, getValue($form, this), msg); this.validate(this, getValue($form, this), msg, deferreds);
} }
if (msg.length) { if (this.enableAjaxValidation) {
messages[this.id] = msg;
} else if (this.enableAjaxValidation) {
needAjaxValidation = true; needAjaxValidation = true;
} }
} }
} }
}); });
$.when.apply(this, deferreds).always(function() {
//Remove empty message arrays
for (var i in messages) {
if (0 === messages[i].length) {
delete messages[i];
}
}
if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) {
// Perform ajax validation when at least one input needs it. // Perform ajax validation when at least one input needs it.
// If the validation is triggered by form submission, ajax validation // If the validation is triggered by form submission, ajax validation
@@ -344,6 +364,7 @@
} else { } else {
successCallback(messages); successCallback(messages);
} }
});
}; };
/** /**

View File

@@ -703,7 +703,7 @@ class ActiveField extends Component
} }
} }
if (!empty($validators)) { if (!empty($validators)) {
$options['validate'] = new JsExpression("function (attribute, value, messages) {" . implode('', $validators) . '}'); $options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}');
} }
} }