Skip to content

Incorrect Class Concatenation for col-md-2 and col-md-10 in Horizontal Forms (ActiveField::initHorizontal) #146

@osricardodiaz

Description

@osricardodiaz

Description:

In kartik-v/yii2-widget-activeform version 1.6.4, the initHorizontal() method in ActiveField.php incorrectly concatenates default grid classes (col-md-2 for and col-md-10 for wrappers) with user-defined classes specified in horizontalCssClasses['label'] and horizontalCssClasses['wrapper'] for horizontal forms (ActiveForm::TYPE_HORIZONTAL). This results in duplicate or unwanted classes, such as col-md-8 has-star col-md-2 col-md-8 for labels and col-md-10 col-md-16 for input wrappers, even when custom classes are explicitly defined.

The issue occurs because the method does not replace the default classes calculated from labelSpan (default 2, yielding col-md-2 and col-md-10) when horizontalCssClasses is provided. Instead, it appends the custom classes, leading to incorrect grid layouts. Additionally, the logic to nullify labelSpan when horizontalCssClasses['wrapper'] contains a col-* class is unreliable, as it fails under certain conditions (e.g., when wrapper is processed as an array or due to configuration overrides).

Environment:

Package: kartik-v/yii2-widget-activeform:1.6.4 (probably it could be there since >1.6.0)
Yii2 Version: yiisoft/yii2:2.0.49
Bootstrap Dependency: yiisoft/yii2-bootstrap:2.x (Bootstrap 3)
PHP Version: 7.4 or higher

Steps to Reproduce:

  1. Create a horizontal form with custom grid classes:
use kartik\form\ActiveForm;

$form = ActiveForm::begin([
    'type' => ActiveForm::TYPE_HORIZONTAL,
    'fieldConfig' => [
        'horizontalCssClasses' => [
            'label' => 'col-md-8',
            'wrapper' => 'col-md-16',
        ],
    ],
]);
echo $form->field($model, 'test')->textInput();
ActiveForm::end();

Render the form and inspect the generated HTML.

Expected Behavior:

The should have classes: control-label col-md-8 has-star (assuming showRequiredIndicator = true).
The wrapper

should have classes: col-md-16.

Actual Behavior:

The has classes: col-md-8 has-star col-md-2 col-md-8 (duplicate col-md-8 and unwanted col-md-2).
The wrapper

has classes: col-md-10 col-md-16 (unwanted col-md-10).
Cause:
In ActiveField::initHorizontal() (lines 1025-1088 in src/ActiveField.php):

The default _labelCss is set to col-md-2 and _inputCss to col-md-10 based on labelSpan = 2 and fullSpan = 12.
Custom classes from horizontalCssClasses['label'] and horizontalCssClasses['wrapper'] are appended to _labelCss and _inputCss (lines 1077-1080) instead of replacing them.
The condition to nullify labelSpan (if (isset($hor['wrapper']) && Lib::strpos($hor['wrapper'], 'col-') !== false)) does not consistently prevent the default classes from being applied, possibly due to type handling or configuration overrides (e.g., formConfig['labelSpan']).

Proposed Solution:
Modify initHorizontal() to:

Replace _labelCss and _inputCss with horizontalCssClasses['label'] and horizontalCssClasses['wrapper'] when defined, instead of appending.
Ensure labelSpan is nullified when horizontalCssClasses['wrapper'] contains col-*, preventing default classes.
Clear _labelCss and _inputCss when labelSpan is invalid or nullified.
Below is the corrected initHorizontal() method:

protected function initHorizontal()
{
    $hor = $this->horizontalCssClasses;
    $span = $this->getConfigParam('labelSpan', '');
    $size = $this->getConfigParam('deviceSize', '');
    $bsVer = $this->form->getBsVer();
    if ($bsVer > 3) {
        Html::addCssClass($this->options, 'row');
    }
    // check horizontalCssClasses['wrapper'] if there is a col- class
    if (isset($hor['wrapper']) && (is_string($hor['wrapper']) && strpos($hor['wrapper'], 'col-') !== false)) {
        $span = '';
    }
    if (empty($span) && !isset($hor['wrapper'])) {
        $span = $this->_settings['labelSpan'];
    }
    if (empty($size)) {
        $size = ArrayHelper::getValue($this->_settings, 'deviceSize');
    }
    $this->deviceSize = $size;
    if (empty($span)) {
        $span = ActiveForm::DEFAULT_LABEL_SPAN;
    }
    if ($span != self::NOT_SET && intval($span) > 0) {
        $span = intval($span);
        if ($span <= 0 || $span >= $this->form->fullSpan) {
            $span = $this->form->fullSpan;
        }
        $sizes = [ActiveForm::SIZE_TINY, ActiveForm::SIZE_SMALL, ActiveForm::SIZE_MEDIUM, ActiveForm::SIZE_LARGE];
        if ($size == self::NOT_SET || !in_array($size, $sizes)) {
            $size = ActiveForm::SIZE_MEDIUM;
        }
        $this->labelSpan = $span;
        $prefix = $this->getColCss($size);
        $this->_labelCss = isset($hor['label']) ? '' : $prefix.$span;
        $this->_inputCss = isset($hor['wrapper']) ? '' : $prefix.($this->form->fullSpan - $span);
    } else {
        $this->_labelCss = '';
        $this->_inputCss = '';
    }
    if (isset($hor['wrapper'])) {
        $this->_inputCss = implode(' ', (array)$hor['wrapper']);
    }
    if (isset($hor['label'])) {
        $this->_labelCss = implode(' ', (array)$hor['label']);
    }
    if (isset($hor['error'])) {
        Html::addCssClass($this->errorOptions, $hor['error']);
    }
}

Additional Notes:

The issue was observed in a project using Bootstrap 3 (yiisoft/yii2-bootstrap:2.x), but it may also affect Bootstrap 4 setups.
The bug is likely introduced or exacerbated by enhancement #108 in version 1.6.4 (bootstrap grid column CSS size map configuration).
A patch script to apply the fix is available if needed (can be shared upon request).
Please consider applying this fix in the next release to ensure proper grid class handling in horizontal forms.

Attachments:

Example form code (above).
Proposed initHorizontal() method (above).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions