From 518634373409bd050b8d1e50cb486611603d89f1 Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Mon, 28 Jul 2014 09:52:21 +0100 Subject: [PATCH 1/9] deferred validation Added support of deferred to ```ActiveForm``` validation. --- framework/assets/yii.activeForm.js | 102 +++++++++++++++-------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 9f8c6159d3..33310d4d32 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -280,70 +280,72 @@ var validate = function ($form, successCallback, errorCallback) { var data = $form.data('yiiActiveForm'), needAjaxValidation = false, - messages = {}; + messages = {}, + deferreds = []; $.each(data.attributes, function () { if (data.submitting || this.status === 2 || this.status === 3) { var msg = []; + messages[this.id] = msg; if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) { if (this.validate) { - this.validate(this, getValue($form, this), msg); + this.validate(this, getValue($form, this), msg, deferreds); } - if (msg.length) { - messages[this.id] = msg; - } else if (this.enableAjaxValidation) { + if (this.enableAjaxValidation) { needAjaxValidation = true; } } } }); - if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { - // Perform ajax validation when at least one input needs it. - // If the validation is triggered by form submission, ajax validation - // should be done only when all inputs pass client validation - var $button = data.submitObject, - extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); - if ($button && $button.length && $button.prop('name')) { - extData += '&' + $button.prop('name') + '=' + $button.prop('value'); - } - $.ajax({ - url: data.settings.validationUrl, - type: $form.prop('method'), - data: $form.serialize() + extData, - dataType: data.settings.ajaxDataType, - complete: function (jqXHR, textStatus) { - if (data.settings.ajaxComplete) { - data.settings.ajaxComplete($form, jqXHR, textStatus); - } - }, - beforeSend: function (jqXHR, textStatus) { - if (data.settings.ajaxBeforeSend) { - data.settings.ajaxBeforeSend($form, jqXHR, textStatus); - } - }, - success: function (msgs) { - if (msgs !== null && typeof msgs === 'object') { - $.each(data.attributes, function () { - if (!this.enableAjaxValidation) { - delete msgs[this.id]; - } - }); - 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 () { + $.when.apply(this, deferreds).always(function() { + if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { + // Perform ajax validation when at least one input needs it. + // If the validation is triggered by form submission, ajax validation + // should be done only when all inputs pass client validation + var $button = data.submitObject, + extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); + if ($button && $button.length && $button.prop('name')) { + extData += '&' + $button.prop('name') + '=' + $button.prop('value'); + } + $.ajax({ + url: data.settings.validationUrl, + type: $form.prop('method'), + data: $form.serialize() + extData, + dataType: data.settings.ajaxDataType, + complete: function (jqXHR, textStatus) { + if (data.settings.ajaxComplete) { + data.settings.ajaxComplete($form, jqXHR, textStatus); + } + }, + beforeSend: function (jqXHR, textStatus) { + if (data.settings.ajaxBeforeSend) { + data.settings.ajaxBeforeSend($form, jqXHR, textStatus); + } + }, + success: function (msgs) { + if (msgs !== null && typeof msgs === 'object') { + $.each(data.attributes, function () { + if (!this.enableAjaxValidation) { + delete msgs[this.id]; + } + }); + 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); - }, 200); - } else { - successCallback(messages); - } + } + }); }; /** From b4ca343bd2e4ce1ae8415fda5366aac39d11600d Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Mon, 28 Jul 2014 09:54:23 +0100 Subject: [PATCH 2/9] deferred validation Added support of deferred validation. --- framework/widgets/ActiveField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 956e0d9ce8..636db97fe5 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -703,7 +703,7 @@ class ActiveField extends Component } } if (!empty($validators)) { - $options['validate'] = new JsExpression("function (attribute, value, messages) {" . implode('', $validators) . '}'); + $options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}'); } } From 334b3130209a528317652f7bfbb08af85e6d12cb Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Mon, 28 Jul 2014 16:23:29 +0100 Subject: [PATCH 3/9] Update yii.activeForm.js remove empty message arrays --- framework/assets/yii.activeForm.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 33310d4d32..277fbeb9ce 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -299,6 +299,12 @@ }); $.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))) { // Perform ajax validation when at least one input needs it. // If the validation is triggered by form submission, ajax validation From 66e0e8aafb2563217821fa2279506fb6782c11b0 Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Mon, 28 Jul 2014 17:14:44 +0100 Subject: [PATCH 4/9] Update yii.activeForm.js --- framework/assets/yii.activeForm.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 277fbeb9ce..6ece00c5d8 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -270,7 +270,22 @@ }); }, 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) { + var deferred = new $.Deferred(); + callback.call(deferred); + this.push(deferred); + }; + return array; + }; + /** * Performs validation. * @param $form jQuery the jquery representation of the form @@ -281,7 +296,7 @@ var data = $form.data('yiiActiveForm'), needAjaxValidation = false, messages = {}, - deferreds = []; + deferreds = deferredArray(); $.each(data.attributes, function () { if (data.submitting || this.status === 2 || this.status === 3) { From 10eedae1131e3f329ccb23b61f5ac68cdefb3b3f Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Tue, 29 Jul 2014 09:23:41 +0100 Subject: [PATCH 5/9] Update input-validation.md --- docs/guide/input-validation.md | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index 8acfb6e322..a8a5e78c95 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -487,6 +487,7 @@ predefined variables: - `attribute`: the name of the attribute being validated. - `value`: the value being validated. - `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 against the existing status data. The validator supports both server side and client side validation. @@ -535,6 +536,56 @@ 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 << 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() { + //Asynchronous Validation here + //The context of this function is the Deferred object where resolve can be called. +}); +``` + ### Ajax validation Some kind of validation can only be done on server side because only the server has the necessary information From 4d337c30685eb46eb78f2482f7626588c1b3eec4 Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Wed, 30 Jul 2014 14:12:04 +0100 Subject: [PATCH 6/9] Update CHANGELOG.md --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c6a03398a8..fefaa9baea 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -161,6 +161,7 @@ Yii Framework 2 Change Log - Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) - Enh #4360: Added client validation support for file validator (Skysplit) - Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) +- Enh #4485: Added support for deferred validation in `ActiveForm` - 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: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) From 6d2bc729c032231e3e0e1a012fe6b7b9df0116b1 Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Wed, 30 Jul 2014 14:12:32 +0100 Subject: [PATCH 7/9] Update CHANGELOG.md --- framework/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fefaa9baea..01139853d9 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -161,7 +161,7 @@ Yii Framework 2 Change Log - Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) - Enh #4360: Added client validation support for file validator (Skysplit) - Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) -- Enh #4485: Added support for deferred validation in `ActiveForm` +- Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code) - 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: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) From 3a10c4cfa472f9ee329451b8e8a92452511336bf Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Wed, 30 Jul 2014 14:24:47 +0100 Subject: [PATCH 8/9] Update yii.activeForm.js Refactored --- framework/assets/yii.activeForm.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 6ece00c5d8..bab3fb5936 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -279,9 +279,7 @@ var deferredArray = function () { var array = []; array.add = function(callback) { - var deferred = new $.Deferred(); - callback.call(deferred); - this.push(deferred); + this.push(new $.Deferred(callback)); }; return array; }; From b60710071fbe52018fbdd3574f252c814c0755af Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Wed, 30 Jul 2014 14:34:47 +0100 Subject: [PATCH 9/9] Update input-validation.md Updated guide --- docs/guide/input-validation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index a8a5e78c95..532945d211 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -580,11 +580,12 @@ deferred.push($.get("/check", {value: value}).done(function(data) { The ```deferred``` array also has a shortcut method ```add```. ``` -deferred.add(function() { +deferred.add(function(def) { //Asynchronous Validation here - //The context of this function is the Deferred object where resolve can be called. + //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