mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-25 03:01:21 +08:00
Merge branch 'deferred-validation' of github.com:Alex-Code/yii2 into Alex-Code-deferred-validation
Conflicts: framework/CHANGELOG.md
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,70 +293,78 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) {
|
$.when.apply(this, deferreds).always(function() {
|
||||||
// Perform ajax validation when at least one input needs it.
|
//Remove empty message arrays
|
||||||
// If the validation is triggered by form submission, ajax validation
|
for (var i in messages) {
|
||||||
// should be done only when all inputs pass client validation
|
if (0 === messages[i].length) {
|
||||||
var $button = data.submitObject,
|
delete messages[i];
|
||||||
extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id');
|
}
|
||||||
if ($button && $button.length && $button.prop('name')) {
|
|
||||||
extData += '&' + $button.prop('name') + '=' + $button.prop('value');
|
|
||||||
}
|
}
|
||||||
$.ajax({
|
if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) {
|
||||||
url: data.settings.validationUrl,
|
// Perform ajax validation when at least one input needs it.
|
||||||
type: $form.prop('method'),
|
// If the validation is triggered by form submission, ajax validation
|
||||||
data: $form.serialize() + extData,
|
// should be done only when all inputs pass client validation
|
||||||
dataType: data.settings.ajaxDataType,
|
var $button = data.submitObject,
|
||||||
complete: function (jqXHR, textStatus) {
|
extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id');
|
||||||
if (data.settings.ajaxComplete) {
|
if ($button && $button.length && $button.prop('name')) {
|
||||||
data.settings.ajaxComplete($form, jqXHR, textStatus);
|
extData += '&' + $button.prop('name') + '=' + $button.prop('value');
|
||||||
}
|
}
|
||||||
},
|
$.ajax({
|
||||||
beforeSend: function (jqXHR, textStatus) {
|
url: data.settings.validationUrl,
|
||||||
if (data.settings.ajaxBeforeSend) {
|
type: $form.prop('method'),
|
||||||
data.settings.ajaxBeforeSend($form, jqXHR, textStatus);
|
data: $form.serialize() + extData,
|
||||||
}
|
dataType: data.settings.ajaxDataType,
|
||||||
},
|
complete: function (jqXHR, textStatus) {
|
||||||
success: function (msgs) {
|
if (data.settings.ajaxComplete) {
|
||||||
if (msgs !== null && typeof msgs === 'object') {
|
data.settings.ajaxComplete($form, jqXHR, textStatus);
|
||||||
$.each(data.attributes, function () {
|
}
|
||||||
if (!this.enableAjaxValidation) {
|
},
|
||||||
delete msgs[this.id];
|
beforeSend: function (jqXHR, textStatus) {
|
||||||
}
|
if (data.settings.ajaxBeforeSend) {
|
||||||
});
|
data.settings.ajaxBeforeSend($form, jqXHR, textStatus);
|
||||||
successCallback($.extend({}, messages, msgs));
|
}
|
||||||
} else {
|
},
|
||||||
successCallback(messages);
|
success: function (msgs) {
|
||||||
}
|
if (msgs !== null && typeof msgs === 'object') {
|
||||||
},
|
$.each(data.attributes, function () {
|
||||||
error: errorCallback
|
if (!this.enableAjaxValidation) {
|
||||||
});
|
delete msgs[this.id];
|
||||||
} else if (data.submitting) {
|
}
|
||||||
// delay callback so that the form can be submitted without problem
|
});
|
||||||
setTimeout(function () {
|
successCallback($.extend({}, messages, msgs));
|
||||||
|
} else {
|
||||||
|
successCallback(messages);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: errorCallback
|
||||||
|
});
|
||||||
|
} else if (data.submitting) {
|
||||||
|
// delay callback so that the form can be submitted without problem
|
||||||
|
setTimeout(function () {
|
||||||
|
successCallback(messages);
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
successCallback(messages);
|
successCallback(messages);
|
||||||
}, 200);
|
}
|
||||||
} else {
|
});
|
||||||
successCallback(messages);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) . '}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user