diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 18f930e1e1..8b11150a1e 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -175,6 +175,7 @@ Yii Framework 2 Change Log - Fixed PBKDF2 key truncation. - Adjusted API. - Enh #4209: Added `beforeCopy`, `afterCopy`, `forceCopy` properties to AssetManager (cebe) +- Enh #4225: Added `ActiveForm::validateOnBlur` and `ActiveField::validateOnBlur` (qiangxue) - Enh #4297: Added check for DOM extension to requirements (samdark) - Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) - Enh #4360: Added client validation support for file validator (Skysplit) diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 185622d9e6..05517fdb9f 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -79,6 +79,8 @@ encodeError: true, // whether to perform validation when a change is detected on the input validateOnChange: false, + // whether to perform validation when the input loses focus + validateOnBlur: false, // whether to perform validation when the user is typing. validateOnType: false, // number of milliseconds that the validation should be delayed when a user is typing in the input field. @@ -235,7 +237,10 @@ if (attribute.validateOnChange) { $input.on('change.yiiActiveForm',function () { validateAttribute($form, attribute, false); - }).on('blur.yiiActiveForm', function () { + }); + } + if (attribute.validateOnBlur) { + $input.on('blur.yiiActiveForm', function () { if (attribute.status == 0 || attribute.status == 1) { validateAttribute($form, attribute, !attribute.status); } diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 3436af4c72..35d9002869 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -95,10 +95,15 @@ class ActiveField extends Component */ public $enableAjaxValidation; /** - * @var boolean whether to perform validation when the input field loses focus and its value is found changed. + * @var boolean whether to perform validation when the value of the input field is changed. * If not set, it will take the value of [[ActiveForm::validateOnChange]]. */ public $validateOnChange; + /** + * @var boolean whether to perform validation when the input field loses focus. + * If not set, it will take the value of [[ActiveForm::validateOnBlur]]. + */ + public $validateOnBlur; /** * @var boolean whether to perform validation while the user is typing in the input field. * If not set, it will take the value of [[ActiveForm::validateOnType]]. @@ -717,7 +722,7 @@ class ActiveField extends Component $inputID = Html::getInputId($this->model, $this->attribute); $options['id'] = $inputID; $options['name'] = $this->attribute; - foreach (['validateOnChange', 'validateOnType', 'validationDelay'] as $name) { + foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) { $options[$name] = $this->$name === null ? $this->form->$name : $this->$name; } $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID"; diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 14e33ac11d..691f86ce25 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -100,10 +100,15 @@ class ActiveForm extends Widget */ public $validateOnSubmit = true; /** - * @var boolean whether to perform validation when an input field loses focus and its value is found changed. + * @var boolean whether to perform validation when the value of an input field is changed. * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field. */ public $validateOnChange = true; + /** + * @var boolean whether to perform validation when an input field loses focus. + * If [[ActiveField::$validateOnBlur]] is set, its value will take precedence for that input field. + */ + public $validateOnBlur = true; /** * @var boolean whether to perform validation while the user is typing in an input field. * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field. diff --git a/tests/unit/framework/widgets/ActiveFieldTest.php b/tests/unit/framework/widgets/ActiveFieldTest.php index f4b3e2af1c..952a3284f0 100644 --- a/tests/unit/framework/widgets/ActiveFieldTest.php +++ b/tests/unit/framework/widgets/ActiveFieldTest.php @@ -268,12 +268,14 @@ EOD; $actualValue = $this->activeField->getClientOptions(); $expectedJsExpression = "function (attribute, value, messages, deferred) {return true;}"; $expectedValidateOnChange = true; + $expectedValidateOnBlur = true; $expectedValidateOnType = false; $expectedValidationDelay = 200; $actualJsExpression = $actualValue['validate']; $this->assertEquals($expectedJsExpression, $actualJsExpression->expression); $this->assertTrue($expectedValidateOnChange === $actualValue['validateOnChange']); + $this->assertTrue($expectedValidateOnBlur === $actualValue['validateOnBlur']); $this->assertTrue($expectedValidateOnType === $actualValue['validateOnType']); $this->assertTrue($expectedValidationDelay === $actualValue['validationDelay']); } @@ -288,6 +290,7 @@ EOD; $actualValue = $this->activeField->getClientOptions(); $expectedJsExpression = "function (attribute, value, messages, deferred) {return true;}"; $expectedValidateOnChange = true; + $expectedValidateOnBlur = true; $expectedValidateOnType = false; $expectedValidationDelay = 200; $expectedError = ".help-block"; @@ -295,6 +298,7 @@ EOD; $actualJsExpression = $actualValue['validate']; $this->assertEquals($expectedJsExpression, $actualJsExpression->expression); $this->assertTrue($expectedValidateOnChange === $actualValue['validateOnChange']); + $this->assertTrue($expectedValidateOnBlur === $actualValue['validateOnBlur']); $this->assertTrue($expectedValidateOnType === $actualValue['validateOnType']); $this->assertTrue($expectedValidationDelay === $actualValue['validationDelay']); $this->assertTrue(1 === $actualValue['enableAjaxValidation']); @@ -317,6 +321,7 @@ EOD; . "{ return 'yii2' == 'yii2'; }(attribute, value)) { return true; }}"; $expectedValidateOnChange = true; + $expectedValidateOnBlur = true; $expectedValidateOnType = false; $expectedValidationDelay = 200; $expectedError = ".help-block"; @@ -324,6 +329,7 @@ EOD; $actualJsExpression = $actualValue['validate']; $this->assertEquals($expectedJsExpression, $actualJsExpression->expression); $this->assertTrue($expectedValidateOnChange === $actualValue['validateOnChange']); + $this->assertTrue($expectedValidateOnBlur === $actualValue['validateOnBlur']); $this->assertTrue($expectedValidateOnType === $actualValue['validateOnType']); $this->assertTrue($expectedValidationDelay === $actualValue['validationDelay']); $this->assertTrue(1 === $actualValue['enableAjaxValidation']); @@ -364,7 +370,7 @@ class ActiveFieldExtend extends ActiveField } /** - * Usefull to test other methods from ActiveField, that call ActiveField::getClientOptions() + * Useful to test other methods from ActiveField, that call ActiveField::getClientOptions() * but it's return value is not relevant for the test being run. */ public function getClientOptions()