yii2-bootstrap4 icon indicating copy to clipboard operation
yii2-bootstrap4 copied to clipboard

ActiveForm: validation error not showing with Bootstrap 4 when using custom input template

Open david-it opened this issue 4 years ago • 14 comments

The issue is related to the extension yiisoft/yii2-bootstrap4 but I think it is due to a compatibility issue of yiisoft/yii2

What steps will reproduce the problem?

Model

class TestForm extends \yii\base\Model
{
    public $demo;
    public function rules()
    {
        return [
            // define validation rules here
            [['demo'], 'required'],
            [['demo'], 'string', 'length' => [2, 4]],
        ];
    }
}

View The input error is manually separated from the input field (for example, the error is needed at the top of the form during ajax validation)

<?php $form = ActiveForm::begin(['id' => 'test-form',
'options' => [
	'class' => 'form-horizontal'],
	'enableAjaxValidation' => true,
	'enableClientValidation' => false,
]) 
?>

<?= $form->errorSummary($model) ?>

<div class="form-row"> 
<div class="form-group col-md-3">
	<?php echo $form->field($model, 'demo', ['template' => "{error}"]); ?>
</div>
</div>

<div class="form-row"> 
<div class="form-group col-md-3">
	<?= $form->field($model, 'demo', ['errorOptions' => ['tag' => false]])->textInput()->label("Demo") ?>
</div>
</div> 

Controller

    public function actionTesterror()
    {
        $model = new TestForm();

        // Ajax validation
        if (Yii::$app->request->isAjax){
            $model->load(Yii::$app->request->post());
            Yii::$app->response->format = Response::FORMAT_JSON;
            $ret = ActiveForm::validate($model);
            Yii::info("Test Error Visualization: " . json_encode($ret));
            return $ret;
        }    
        return $this->render('testerror', ['model' => $model]);
    }

What is the expected result?

The error message Demo cannot be blank is visible.

What do you get instead?

The error message Demo cannot be blank is not visible.

Additional info

The issue is related to how the visualization of the error messages are handled in Bootstrap 4.

The {error} of the input demo should be displayed in a <div> which does not contain the actual input tag.

In Bootstrap 4 this creates a visualization issue due to the fact that the validation errors are set with display:block or display:none depending on the class of a sibling tag (i.e. the input tag). The css code contains in fact the “sibling combinator” ~.

.was-validated :invalid ~ .invalid-feedback,
.was-validated :invalid ~ .invalid-tooltip,
.is-invalid ~ .invalid-feedback,
.is-invalid ~ .invalid-tooltip {
  display: block;
}

Working solution

Two additional lines can be added to the function updateInput in yii.activeForm.js, in order to force the setting of "display": "block":

// Patch: show errors when input {error} is not in the default position
$(attribute.container).children(attribute.error).css({"display": "block"});
// ...
// Patch: hide errors when input {error} is not in the default position
$(attribute.container).children(attribute.error).css({"display": "none"});

I have the modified version of yii.activeForm.js in the branch "custom" of my fork david-it/yii2-framework

Q A
Yii version 2.0.32
yii2-bootstrap4 2.0.8.0
PHP version 7.3.14
Operating system Raspbian Buster

david-it avatar Mar 22 '20 21:03 david-it

Have you setup up properly ActiveForm with BS4 horizontal layout with proper namespace?

use yii\bootstrap4\ActiveForm;

$form = ActiveForm::begin([
    'layout' => 'horizontal',
    'fieldConfig' => [
        'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}",
        'horizontalCssClasses' => [
            'label' => 'col-sm-4 text-right',
            'offset' => 'col-sm-offset-4',
            'wrapper' => 'col-sm-7',
            'error' => '',
            'hint' => '',
        ],
    ],
]);
...
<?= $form->field($model, 'myAttribute')->textInput() ?> // works OK with BS3 & BS4
...
<?php ActiveForm::end() ?>

See documentation. BS4 has changed some of DOM structures in standard elements. However, changing existing jquery selector could break sites build with BS3. It's sometime necessary to adjust option template for particular element and define own layout. In my experience this is only case with e.g. prepend/append input groups or complex lists (radiolist, checkboxlist). Otherwise Yii2 ActiveForm works out of the box also with BS4.

lubosdz avatar Mar 24 '20 20:03 lubosdz

I'm afraid that your example is quite different from what I'm trying to point out. I've shown above a specific example of the issue.

To make it more clear, this works perfect with bootstrap 4:

use yii\bootstrap4\ActiveForm;
...
<?php
  $form = ActiveForm::begin(['id' => 'test-form',
    'options' => [
    'class' => 'form-horizontal'],
    'enableAjaxValidation' => true,
    'enableClientValidation' => false,
  ])
?>
<?= $form->field($model, 'demo')->textInput()->label("Demo") ?>
<?php ActiveForm::end() ?>

This does not work with Bootstrap 4:

use yii\bootstrap4\ActiveForm;
...
<?php
  $form = ActiveForm::begin(['id' => 'test-form',
    'options' => [
    'class' => 'form-horizontal'],
    'enableAjaxValidation' => true,
    'enableClientValidation' => false,
  ])
?>
<?= $form->field($model, 'demo', ['template' => "{error}"]); ?>
<?= $form->field($model, 'demo', ['errorOptions' => ['tag' => false]])->textInput()->label("Demo") ?>
<?php ActiveForm::end() ?>

As a use case, you may want to show the error somewhere far away from the input. For example at the top of the form.

david-it avatar Mar 25 '20 10:03 david-it

I think to show the error(s) on top of the form, you should use ErrorSummary.

simialbi avatar Mar 25 '20 19:03 simialbi

During ajax validation the ErrorSummary is not shown. The function updateSummary (in yii.activeForm.js) is called only if the form is submitted.

In any case, this does not explain why the code above is not supposed to work. Since there is the possibility to separate the {error} from the input there should be also the possibility to display it.

I know many things are different but in Yii1 there is a very similar way to separate the error messages and it works right away.

david-it avatar Mar 25 '20 19:03 david-it

Well, if you isolate the error (by changing template content) then you actually break expected DOM elements structure - so it won't work. Though valid, your use case is not standard implementation, unfortunatelly. I see 3 possible solutions:

  • either to modify JS handler for yii.activeForm
  • or dynamically (on page ready) rename original ID and inject DOM element wherever you need to (or if possible move whole field container while keeping DOM structure)
  • add you custom CSS to highlight error

lubosdz avatar Mar 25 '20 20:03 lubosdz

Hello. I don't know if my issue is directly related with this, but I think it can be. I'm submitting a form with ajax. When the form comes with validation errors, I got this: form.yiiActiveForm("updateMessages", data.validation, true); in the javascript part.

The issue is, if I use yii\widgets\ActiveForm works ok, but If I use yii\bootstrap4\ActiveForm, the errors not showing up.

<?php $form = ActiveForm::begin([
           'id' => 'orden-items-create-form-id',
           'enableClientValidation' => true,
           'enableAjaxValidation' => true,
           ],
 ]); ?>

Also, I tried with 'enableClientValidation' => false ... the form is submitted but when it comes back with errors, these not showing up either.

Any ideas?

Thanks

leocharrua avatar Sep 15 '21 13:09 leocharrua

No. I guess further debugging is needed in order to come up with the reason for it.

samdark avatar Sep 18 '21 21:09 samdark

How I can help?

leocharrua avatar Sep 20 '21 20:09 leocharrua

  1. Check console. Are there any errors?
  2. Inspect HTML. Are there error classes added? If so, are there styles for these?

samdark avatar Sep 22 '21 19:09 samdark

Hello. After a debug session, I think "form.yiiActiveForm" is not the problem ... really, it nevers called, because "beforeSubmit" is not called eather. I'm doing a Ajax validation and this is the context:

Form:

$form = ActiveForm::begin([
            'id' => 'orden-items-create-form-id',
            'enableClientValidation' => false,
            'enableAjaxValidation' => true,
            'validationUrl' => ['/orden-items/validate-create-from-orden', 'orden_id' => $orden_id],
            'action' => ['/orden-items/create-from-orden', 'orden_id' => $orden_id],
        ]);

The controller:

public function actionValidateCreateFromOrden($orden_id)
    {
        $model = new OrdenItems(['scenario' => 'insert']);
        $model->estado_c = 'A';
        $model->orden_id = $orden_id;

        if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
            Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
            return \yii\bootstrap4\ActiveForm::validate($model);
            // return \yii\widgets\ActiveForm::validate($model);
        }
    }

(here I tested with boostrap4 and widgets validate, but the result its the same).

In the form when I use yii\widgets\ActiveForm works ok, but when I use yii\bootstrap4\ActiveForm, nothing happens (no error is shown).

Any ideas? Thanks

widget_form_1 widget_form_2 bs4_form_1 bs4_form_2 bs4_form_3

leocharrua avatar Sep 24 '21 13:09 leocharrua

It is in HTML in both cases, right? If so, that's CSS issue. There's basically no class for it.

samdark avatar Sep 24 '21 18:09 samdark

I don't understand ... The css is the same ... why is working ok with yii\widgets\ActiveForm but not with yii\bootstrap4\ActiveForm? Can you explain a little more? Thanks

leocharrua avatar Sep 24 '21 18:09 leocharrua

the same problem with bootstrap5

mgrechanik avatar May 08 '24 07:05 mgrechanik