diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2ec245ea228..64e997ee82e 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.50 under development ------------------------ +- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher) - Bug #19927: Fixed `console\controllers\MessageController` when saving translations to database: fixed FK error when adding new string and language at the same time, checking/regenerating all missing messages and dropping messages for unused languages (atrandafir) - Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index b12f812c37d..5b9ce4aaec2 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -395,9 +395,11 @@ data: $form.serialize() + extData, dataType: data.settings.ajaxDataType, complete: function (jqXHR, textStatus) { + currentAjaxRequest = null; $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); }, beforeSend: function (jqXHR, settings) { + currentAjaxRequest = jqXHR; $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); }, success: function (msgs) { @@ -563,6 +565,9 @@ return; } + if (currentAjaxRequest !== null) { + currentAjaxRequest.abort(); + } if (data.settings.timer !== undefined) { clearTimeout(data.settings.timer); } @@ -929,4 +934,7 @@ $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false'); } } + + var currentAjaxRequest = null; + })(window.jQuery); diff --git a/tests/js/data/yii.activeForm.html b/tests/js/data/yii.activeForm.html index d44c9f1282b..d278be5b8b8 100644 --- a/tests/js/data/yii.activeForm.html +++ b/tests/js/data/yii.activeForm.html @@ -48,3 +48,15 @@
+
+
+ + +
+
+
+ + +
+
+
diff --git a/tests/js/tests/yii.activeForm.test.js b/tests/js/tests/yii.activeForm.test.js index 16671239ea3..f79599b0747 100644 --- a/tests/js/tests/yii.activeForm.test.js +++ b/tests/js/tests/yii.activeForm.test.js @@ -27,6 +27,21 @@ describe('yii.activeForm', function () { var script = new vm.Script(code); var context = new vm.createContext({window: window, document: window.document, yii: yii}); script.runInContext(context); + /** This is a workaround for a jsdom issue, that prevents :hidden and :visible from working as expected. + * @see https://github.com/jsdom/jsdom/issues/1048 */ + context.window.Element.prototype.getClientRects = function () { + var node = this; + while(node) { + if(node === document) { + break; + } + if (!node.style || node.style.display === 'none' || node.style.visibility === 'hidden') { + return []; + } + node = node.parentNode; + } + return [{width: 100, height: 100}]; + }; } var activeFormHtml = fs.readFileSync('tests/js/data/yii.activeForm.html', 'utf-8'); @@ -117,6 +132,60 @@ describe('yii.activeForm', function () { assert.isFalse($activeForm.data('yiiActiveForm').validated); }); }); + + describe('with ajax validation', function () { + describe('with rapid validation of multiple fields', function () { + it('should cancel overlapping ajax requests and not display outdated validation results', function () { + $activeForm = $('#w3'); + $activeForm.yiiActiveForm([{ + id: 'test-text2', + input: '#test-text2', + container: '.field-test-text2', + enableAjaxValidation: true + }, { + id: 'test-text3', + input: '#test-text3', + container: '.field-test-text3', + enableAjaxValidation: true + }], { + validationUrl: '' + }); + + let requests = []; + function fakeAjax(object) { + const request = { + jqXHR: { + abort: function () { + request.aborted = true; + } + }, + aborted: false, + respond: function (response) { + if (this.aborted) { + return; + } + object.success(response); + object.complete(this.jqXHR, ''); + } + }; + requests.push(request); + object.beforeSend(request.jqXHR, ''); + } + + const ajaxStub = sinon.stub($, 'ajax', fakeAjax); + $activeForm.yiiActiveForm('validateAttribute', 'test-text2'); + assert.isTrue(requests.length === 1); + $activeForm.yiiActiveForm('validateAttribute', 'test-text3'); + // When validateAttribute was called on text2, its value was valid. + // The value of text3 wasn't. + requests[0].respond({'test-text3': ['Field cannot be empty']}); + // When validateAttribute was called on text3, its value was valid. + requests[1].respond([]); + assert.isTrue($activeForm.find('.field-test-text3').hasClass('has-success')); + ajaxStub.restore(); + }); + }); + }) }); describe('resetForm method', function () {