Skip to content

Commit

Permalink
Fixes yiisoft#15476: Added `\yii\widgets\ActiveForm::$validationState…
Browse files Browse the repository at this point in the history
…On` to be able to specify where to add class for invalid fields
  • Loading branch information
samdark authored Jan 30, 2018
1 parent 06ebd3f commit b3130be
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 8 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 13 additions & 5 deletions framework/assets/yii.activeForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
74 changes: 71 additions & 3 deletions framework/widgets/ActiveField.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,10 @@ public function begin()
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);
Expand Down Expand Up @@ -363,6 +363,10 @@ public function hint($content, $options = [])
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);
Expand Down Expand Up @@ -390,6 +394,11 @@ public function input($type, $options = [])
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);
Expand Down Expand Up @@ -436,6 +445,11 @@ public function hiddenInput($options = [])
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);
Expand Down Expand Up @@ -464,6 +478,11 @@ public function fileInput($options = [])
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);
Expand All @@ -484,6 +503,11 @@ public function fileInput($options = [])
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);
Expand Down Expand Up @@ -532,6 +556,11 @@ public function radio($options = [], $enclosedByLabel = true)
$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);

Expand Down Expand Up @@ -579,6 +608,11 @@ public function checkbox($options = [], $enclosedByLabel = true)
$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);

Expand Down Expand Up @@ -607,6 +641,11 @@ public function checkbox($options = [], $enclosedByLabel = true)
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);
Expand Down Expand Up @@ -636,6 +675,11 @@ public function dropDownList($items, $options = [])
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);
Expand All @@ -656,6 +700,10 @@ public function listBox($items, $options = [])
*/
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;
Expand All @@ -676,6 +724,10 @@ public function checkboxList($items, $options = [])
*/
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;
Expand Down Expand Up @@ -717,6 +769,10 @@ public function widget($class, $config = [])
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']);
}
Expand Down Expand Up @@ -866,4 +922,16 @@ protected function addAriaAttributes(&$options)
}
}
}

/**
* 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 changes: 21 additions & 0 deletions framework/widgets/ActiveForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@
*/
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.
Expand Down Expand Up @@ -96,6 +108,13 @@ class ActiveForm extends Widget
* @var string the CSS class that is added to a field container when the associated attribute is being validated.
*/
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.
Expand Down Expand Up @@ -244,6 +263,7 @@ protected function getClientOptions()
'ajaxDataType' => $this->ajaxDataType,
'scrollToError' => $this->scrollToError,
'scrollToErrorOffset' => $this->scrollToErrorOffset,
'validationStateOn' => $this->validationStateOn,
];
if ($this->validationUrl !== null) {
$options['validationUrl'] = Url::to($this->validationUrl);
Expand All @@ -261,6 +281,7 @@ protected function getClientOptions()
'ajaxDataType' => 'json',
'scrollToError' => true,
'scrollToErrorOffset' => 0,
'validationStateOn' => self::VALIDATION_STATE_ON_CONTAINER,
]);
}

Expand Down
28 changes: 28 additions & 0 deletions tests/framework/widgets/ActiveFormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,32 @@ public function testShouldTriggerInitEvent()
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'));

}
}

0 comments on commit b3130be

Please sign in to comment.