diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 579411a09a..87ad7609a0 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.37 under development ------------------------ +- Bug #17147: Fix form attribute validations for empty select inputs (kartik-v) - Bug #18171: Change case of column names in SQL query for `findConstraints` to fix MySQL 8 compatibility (darkdef) - Bug #18170: Fix 2.0.36 regression in passing extra console command arguments to the action (darkdef) - Bug #18182: `yii\db\Expression` was not supported as condition in `ActiveRecord::findOne()` and `ActiveRecord::findAll()` (rhertogh) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 6c3a79a7c7..0c1ccb0f77 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -14,11 +14,13 @@ $.fn.yiiActiveForm = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method) { - return methods.init.apply(this, arguments); } else { - $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm'); - return false; + if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm'); + return false; + } } }; @@ -178,14 +180,14 @@ var submitDefer; - var setSubmitFinalizeDefer = function($form) { + var setSubmitFinalizeDefer = function ($form) { submitDefer = $.Deferred(); $form.data('yiiSubmitFinalizePromise', submitDefer.promise()); }; // finalize yii.js $form.submit - var submitFinalize = function($form) { - if(submitDefer) { + var submitFinalize = function ($form) { + if (submitDefer) { submitDefer.resolve(); submitDefer = undefined; $form.removeData('yiiSubmitFinalizePromise'); @@ -327,15 +329,22 @@ this.$form = $form; var $input = findInput($form, this); - if ($input.is(":disabled")) { + if ($input.is(':disabled')) { return true; } - // pass SELECT without options + // validate markup for select input if ($input.length && $input[0].tagName.toLowerCase() === 'select') { - if (!$input[0].options.length) { - return true; - } else if (($input[0].options.length === 1) && ($input[0].options[0].value === '')) { - return true; + var opts = $input[0].options, isEmpty = !opts || !opts.length, isRequired = $input.attr('required'), + isMultiple = $input.attr('multiple'), size = $input.attr('size') || 1; + // check if valid HTML markup for select input, else return validation as `true` + // https://w3c.github.io/html-reference/select.html + if (isRequired && !isMultiple && parseInt(size, 10) === 1) { // invalid select markup condition + if (isEmpty) { // empty option elements for the select + return true; + } + if (opts[0] && (opts[0].value !== '' && opts[0].text !== '')) { // first option is not empty + return true; + } } } this.cancelled = false; @@ -363,7 +372,7 @@ }); // ajax validation - $.when.apply(this, deferreds).always(function() { + $.when.apply(this, deferreds).always(function () { // Remove empty message arrays for (var i in messages) { if (0 === messages[i].length) { @@ -404,13 +413,15 @@ submitFinalize($form); } }); - } else if (data.submitting) { - // delay callback so that the form can be submitted without problem - window.setTimeout(function () { - updateInputs($form, messages, submitting); - }, 200); } else { - updateInputs($form, messages, submitting); + if (data.submitting) { + // delay callback so that the form can be submitted without problem + window.setTimeout(function () { + updateInputs($form, messages, submitting); + }, 200); + } else { + updateInputs($form, messages, submitting); + } } }); }, @@ -459,9 +470,9 @@ $errorElement = data.settings.validationStateOn === 'input' ? $input : $container; $errorElement.removeClass( - data.settings.validatingCssClass + ' ' + - data.settings.errorCssClass + ' ' + - data.settings.successCssClass + data.settings.validatingCssClass + ' ' + + data.settings.errorCssClass + ' ' + + data.settings.successCssClass ); $container.find(this.error).html(''); }); @@ -492,7 +503,7 @@ * @param id attribute ID * @param messages array with error messages */ - updateAttribute: function(id, messages) { + updateAttribute: function (id, messages) { var attribute = methods.find.call(this, id); if (attribute != undefined) { var msg = {}; @@ -518,7 +529,7 @@ } if (attribute.validateOnType) { $input.on('keyup.yiiActiveForm', function (e) { - if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1 ) { + if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1) { return; } if (attribute.value !== getValue($form, attribute)) { @@ -571,7 +582,7 @@ * @param val2 * @returns boolean */ - var isEqual = function(val1, val2) { + var isEqual = function (val1, val2) { // objects if (val1 instanceof Object) { return isObjectsEqual(val1, val2) @@ -592,7 +603,7 @@ * @param obj2 * @returns boolean */ - var isObjectsEqual = function(obj1, obj2) { + var isObjectsEqual = function (obj1, obj2) { if (!(obj1 instanceof Object) || !(obj2 instanceof Object)) { return false; } @@ -621,7 +632,7 @@ * @param arr2 * @returns boolean */ - var isArraysEqual = function(arr1, arr2) { + var isArraysEqual = function (arr1, arr2) { if (!Array.isArray(arr1) || !Array.isArray(arr2)) { return false; } @@ -644,7 +655,7 @@ */ var deferredArray = function () { var array = []; - array.add = function(callback) { + array.add = function (callback) { this.push(new $.Deferred(callback)); }; return array; @@ -707,10 +718,11 @@ var errorAttributes = [], $input; $.each(data.attributes, function () { - var hasError = (submitting && updateInput($form, this, messages)) || (!submitting && attrHasError($form, this, messages)); + var hasError = (submitting && updateInput($form, this, messages)) || (!submitting && attrHasError($form, + this, messages)); $input = findInput($form, this); - if (!$input.is(":disabled") && !this.cancelled && hasError) { + if (!$input.is(':disabled') && !this.cancelled && hasError) { errorAttributes.push(this); } }); @@ -721,14 +733,10 @@ updateSummary($form, messages); if (errorAttributes.length) { if (data.settings.scrollToError) { - var top = $form.find($.map(errorAttributes, function(attribute) { + var h = $(document).height(), top = $form.find($.map(errorAttributes, function (attribute) { return attribute.input; }).join(',')).first().closest(':visible').offset().top - data.settings.scrollToErrorOffset; - if (top < 0) { - top = 0; - } else if (top > $(document).height()) { - top = $(document).height(); - } + top = top < 0 ? 0 : (top > h ? h : top); var wtop = $(window).scrollTop(); if (top < wtop || top > wtop + $(window).height()) { $(window).scrollTop(top); @@ -809,11 +817,11 @@ $error.html(messages[attribute.id][0]); } $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) - .addClass(data.settings.errorCssClass); + .addClass(data.settings.errorCssClass); } else { $error.empty(); $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ') - .addClass(data.settings.successCssClass); + .addClass(data.settings.successCssClass); } attribute.value = getValue($form, attribute); } @@ -878,7 +886,7 @@ var $realInput = $input.filter(':checked'); if ($realInput.length > 1) { var values = []; - $realInput.each(function(index) { + $realInput.each(function (index) { values.push($($realInput.get(index)).val()); }); return values; @@ -909,4 +917,4 @@ $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false'); } } -})(window.jQuery); +})(window.jQuery); \ No newline at end of file