Browse Source

Fixes #15476: Added `\yii\widgets\ActiveForm::$validationStateOn` to be able to specify where to add class for invalid fields

tags/2.0.14
Alexander Makarov 7 years ago committed by GitHub
parent
commit
b3130be7ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      framework/CHANGELOG.md
  2. 18
      framework/assets/yii.activeForm.js
  3. 74
      framework/widgets/ActiveField.php
  4. 21
      framework/widgets/ActiveForm.php
  5. 28
      tests/framework/widgets/ActiveFormTest.php

1
framework/CHANGELOG.md

@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.14 under development
------------------------
- Enh #15476: Added `\yii\widgets\ActiveForm::$validationStateOn` to be able to specify where to add class for invalid fields (samdark)
- Enh #13996: Added `yii\web\View::registerJsVar()` method that allows registering JavaScript variables (Eseperio, samdark)
- Enh #9771: Assign hidden input with its own set of HTML options via `$hiddenOptions` in activeFileInput `$options` (HanafiAhmat)
- Bug #15536: Fixed `yii\widgets\ActiveForm::init()` for call `parent::init()` (panchenkodv)

18
framework/assets/yii.activeForm.js

@ -134,7 +134,9 @@
// whether to scroll to first visible error after validation.
scrollToError: true,
// offset in pixels that should be added when scrolling to the first error.
scrollToErrorOffset: 0
scrollToErrorOffset: 0,
// where to add validation class: container or input
validationStateOn: 'container'
};
// NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well
@ -441,8 +443,11 @@
// Without setTimeout() we would get the input values that are not reset yet.
this.value = getValue($form, this);
this.status = 0;
var $container = $form.find(this.container);
$container.removeClass(
var $container = $form.find(this.container),
$input = findInput($form, attribute),
$errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
$errorElement.removeClass(
data.settings.validatingCssClass + ' ' +
data.settings.errorCssClass + ' ' +
data.settings.successCssClass
@ -711,17 +716,20 @@
var $container = $form.find(attribute.container);
var $error = $container.find(attribute.error);
updateAriaInvalid($form, attribute, hasError);
var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
if (hasError) {
if (attribute.encodeError) {
$error.text(messages[attribute.id][0]);
} else {
$error.html(messages[attribute.id][0]);
}
$container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
$errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
.addClass(data.settings.errorCssClass);
} else {
$error.empty();
$container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
$errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
.addClass(data.settings.successCssClass);
}
attribute.value = getValue($form, attribute);

74
framework/widgets/ActiveField.php

@ -241,10 +241,10 @@ class ActiveField extends Component
if ($this->model->isAttributeRequired($attribute)) {
$class[] = $this->form->requiredCssClass;
}
if ($this->model->hasErrors($attribute)) {
$class[] = $this->form->errorCssClass;
}
$options['class'] = implode(' ', $class);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_CONTAINER) {
$this->addErrorClassIfNeeded($options);
}
$tag = ArrayHelper::remove($options, 'tag', 'div');
return Html::beginTag($tag, $options);
@ -363,6 +363,10 @@ class ActiveField extends Component
public function input($type, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
@ -390,6 +394,11 @@ class ActiveField extends Component
public function textInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
@ -436,6 +445,11 @@ class ActiveField extends Component
public function passwordInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
@ -464,6 +478,11 @@ class ActiveField extends Component
if (!isset($this->form->options['enctype'])) {
$this->form->options['enctype'] = 'multipart/form-data';
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
@ -484,6 +503,11 @@ class ActiveField extends Component
public function textarea($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
@ -532,6 +556,11 @@ class ActiveField extends Component
$options['label'] = null;
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
@ -579,6 +608,11 @@ class ActiveField extends Component
$options['label'] = null;
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
@ -607,6 +641,11 @@ class ActiveField extends Component
public function dropDownList($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
@ -636,6 +675,11 @@ class ActiveField extends Component
public function listBox($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
@ -656,6 +700,10 @@ class ActiveField extends Component
*/
public function checkboxList($items, $options = [])
{
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
@ -676,6 +724,10 @@ class ActiveField extends Component
*/
public function radioList($items, $options = [])
{
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
@ -717,6 +769,10 @@ class ActiveField extends Component
if (is_subclass_of($class, 'yii\widgets\InputWidget')) {
$config['field'] = $this;
if (isset($config['options'])) {
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
}
@ -866,4 +922,16 @@ class ActiveField extends Component
}
}
}
/**
* Adds validation class to the input options if needed.
* @param $options array input options
* @since 2.0.14
*/
protected function addErrorClassIfNeeded(&$options)
{
if ($this->model->hasErrors($this->attribute)) {
Html::addCssClass($options, $this->form->errorCssClass);
}
}
}

21
framework/widgets/ActiveForm.php

@ -27,6 +27,18 @@ use yii\helpers\Url;
class ActiveForm extends Widget
{
/**
* Add validation state class to container tag
* @since 2.0.14
*/
const VALIDATION_STATE_ON_CONTAINER = 'container';
/**
* Add validation state class to input tag
* @since 2.0.14
*/
const VALIDATION_STATE_ON_INPUT = 'input';
/**
* @var array|string the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
* @see method for specifying the HTTP method for this form.
*/
@ -97,6 +109,13 @@ class ActiveForm extends Widget
*/
public $validatingCssClass = 'validating';
/**
* @var string where to render validation state class
* Could be either "container" or "input".
* Default is "container".
* @since 2.0.14
*/
public $validationStateOn = self::VALIDATION_STATE_ON_CONTAINER;
/**
* @var bool whether to enable client-side data validation.
* If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
*/
@ -244,6 +263,7 @@ class ActiveForm extends Widget
'ajaxDataType' => $this->ajaxDataType,
'scrollToError' => $this->scrollToError,
'scrollToErrorOffset' => $this->scrollToErrorOffset,
'validationStateOn' => $this->validationStateOn,
];
if ($this->validationUrl !== null) {
$options['validationUrl'] = Url::to($this->validationUrl);
@ -261,6 +281,7 @@ class ActiveForm extends Widget
'ajaxDataType' => 'json',
'scrollToError' => true,
'scrollToErrorOffset' => 0,
'validationStateOn' => self::VALIDATION_STATE_ON_CONTAINER,
]);
}

28
tests/framework/widgets/ActiveFormTest.php

@ -153,4 +153,32 @@ HTML
ActiveForm::end();
$this->assertTrue($initTriggered);
}
/**
* @see https://github.com/yiisoft/yii2/issues/15476
*/
public function testValidationStateOnInput()
{
$model = new DynamicModel(['name']);
$model->addError('name', 'I have an error!');
ob_start();
$form = ActiveForm::begin([
'action' => '/something',
'enableClientScript' => false,
'validationStateOn' => ActiveForm::VALIDATION_STATE_ON_INPUT,
]);
ActiveForm::end();
ob_end_clean();
$this->assertEqualsWithoutLE(<<<'EOF'
<div class="form-group field-dynamicmodel-name">
<label class="control-label" for="dynamicmodel-name">Name</label>
<input type="text" id="dynamicmodel-name" class="form-control has-error" name="DynamicModel[name]" aria-invalid="true">
<div class="help-block">I have an error!</div>
</div>
EOF
, (string) $form->field($model, 'name'));
}
}

Loading…
Cancel
Save