Как решить конфликт Select2 и Bootstrap 5 в Cotonti Siena (0.9.26+)
При использовании Select2 с Bootstrap 5 в Cotonti Siena часто возникает проблема: после подключения стилей Bootstrap (в частности, класса form-select) Select2 перестаёт работать — поле становится неактивным, пропадает поиск, не отображаются теги.
Это происходит потому, что:
- Bootstrap 5 переопределяет стили и поведение <select> через класс form-select.
- Select2 полностью заменяет DOM-структуру <select> и не совместим с form-select.
- Cotonti по умолчанию использует шаблон $R['input_select'], который вы добавляете form-select ко всем <select>.
Причина конфликта
$R['input_select'] = '<select class="form-select" name="{$name}"{$attrs}>{$options}</select>';Этот шаблон применяется ко всем полям <select>, включая те, где используется Select2. В результате:
<select class="form-select user-input" multiple>...</select>→ Select2 не может инициализироваться → поле "заморожено".
Правильное решение: использование $rc_name в cot_selectbox()
Cotonti умнее, чем кажется. Функция cot_selectbox() автоматически выбирает шаблон по имени поля:
$rc_name = preg_match('#^(\w+)\[(.*?)\]$#', $name, $mt) ? $mt[1] : $name;- rs[setuser][] → $rc_name = 'rs'
- rtags[title] → $rc_name = 'rtags'
- category → $rc_name = 'category'
→ Cotonti ищет шаблон: $R["input_select_{$rc_name}"] → если есть, использует его.
Решение: создаём отдельные шаблоны по префиксу
В файле темы:
themes/ваша_тема/ваша_тема.php
<?php
defined('COT_CODE') or die('Wrong URL.');
global $R;
// Для полей rs[...] — БЕЗ form-select (Select2)
$R['input_select_rs'] = '<select name="{$name}"{$attrs}>{$options}</select>';
// Для полей rtags[...] — тоже без form-select (если используется Select2)
$R['input_select_rtags'] = '<select name="{$name}"{$attrs}>{$options}</select>';
// Для всех остальных — с Bootstrap 5
$R['input_select'] = '<select class="form-select" name="{$name}"{$attrs}>{$options}</select>';Как это работает
| Поле | $rc_name | Шаблон ключа строки $R | form-select |
| rs[setuser][] | rs | input_select_rs | Нет |
| rtags[title] | rtags | input_select_rtags | Нет |
| ruserlang | ruserlang | input_select | Да |
Ничего не меняем в коде
- Не трогаем UsersHelper
- Не переопределяем cot_selectbox()
- Не используем хуки, CSS-хаки или Closure
- Работает в ядре 0.9.26+
Дополнительно: Bootstrap 5 тема для Select2 (по желанию, то есть не обязательно)
Чтобы Select2 выглядел как Bootstrap:
<link href="https://cdn.jsdelivr.net/npm/@ttskch/[email protected]/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />$('.user-input').select2({
theme: 'bootstrap-5',
width: '100%'
});Вывод
Не боритесь с form-select — управляйте шаблонами по имени поля.
$R['input_select_ПРЕФИКС'] = '<select ...>'; // без form-select
$R['input_select'] = '<select class="form-select" ...>'; // для остальныхПросто. Чисто. Лаконично. Без правок ядра. Без ошибок. Без магии. Решение найдено. Не стесняемся сказать спасибо!
Cotonti в действительности ищет ресурс с именем:
$R["input_select_{$rc_name}"]
где $rc_name — это имя поля без квадратных скобок и ДО их обїявления в исходном коде.
Если поле называется, например,
<select name="rs[setuser][]">
то Cotonti будет искать:
$R['input_select_rs']
и только если его нет, тогда уже возьмёт $R['input_select'].
Итого
Решение:
$R['input_select_rtags'] = '<select name="{$name}"{$attrs}>{$options}</select>'; // имя поля rtags
$R['input_select_rs'] = '<select name="{$name}"{$attrs}>{$options}</select>'; // имя поля rs
// Для всех остальных — с Bootstrap 5
$R['input_select'] = '<select class="form-select" name="{$name}"{$attrs}>{$options}</select>';и так по примеру выше для каждого поля, где будет и должен использоваться Select2
— это абсолютно корректный, “канонический” способ убрать Bootstrap-класс только для нужных полей (например, rs[...]), не ломая остальные.
Почему это работает
Cotonti автоматически парсит имя до квадратных скобок:rs[setuser][] → $rc_name = 'rs'
Ищет $R['input_select_rs']
Мы задаем именно этот шаблон без form-select в строке кастомных ресурсов
$R['input_select_rs'] = '<select name="{$name}"{$attrs}>{$options}</select>'; // имя поля rsДля всех остальных остаётся общий $R['input_select'] с Bootstrap
Это нативный способ сегментировать шаблоны по префиксу имени поля, и он даже чище, чем городить хардкором.
Узнать больше - смотреть Form generation API system/forms.php
сначала смотрим переменную
$rc_name
а затем
$rc = empty($R["input_select_{$rc_name}"]) ? (empty($custom_rc) ? 'input_select' : $custom_rc) : "input_select_{$rc_name}";внутри функции
/**
* Renders a dropdown
*
* @param string|string[] $chosen Selected value (or values array for mutli-select)
* @param string $name Dropdown name
* @param string[] $values Options available
* @param string[] $titles Titles for options
* @param bool $add_empty Allow empty choice
* @param string|array<string, string> $attrs Additional attributes as an associative array or a string
* @param string $custom_rc Custom resource string name
* @param bool $htmlspecialcharsBypass Bypass htmlspecialchars() for option titles and values
* @return string
*/
function cot_selectbox(
$chosen,
$name,
$values,
$titles = [],
$add_empty = true,
$attrs = '',
$custom_rc = '',
$htmlspecialcharsBypass = false
) {
global $R, $cfg;
if (!is_array($values)) {
$values = explode(',', $values);
}
if (!is_array($titles)) {
$titles = explode(',', $titles);
}
$msgSeparate = isset($cfg['msg_separate'])? $cfg['msg_separate'] : false;
$use_titles = count($values) == count($titles);
$input_attrs = cot_rc_attr_string($attrs);
$chosen = cot_import_buffered($name, $chosen);
$multi = is_array($chosen) && (mb_strpos($input_attrs, 'multiple') !== false);
$error = $msgSeparate ? cot_implode_messages($name, 'error') : '';
$rc_name = preg_match('#^(\w+)\[(.*?)\]$#', $name, $mt) ? $mt[1] : $name;
$selected = (is_null($chosen) || $chosen === '' || $chosen == '00') ? ' selected="selected"' : '';
$rc = empty($R["input_option_{$rc_name}"]) ? 'input_option' : "input_option_{$rc_name}";
$options = '';
if ($add_empty) {
$options .= cot_rc($rc, [
'value' => '',
'selected' => $selected,
'title' => $R['code_option_empty']
]);
}
foreach ($values as $k => $x) {
$x = trim($x);
$selected = ($multi && in_array($x, $chosen)) || (!$multi && $x == $chosen) ? ' selected="selected"' : '';
$title = ($use_titles && !empty($titles[$k])) ? $titles[$k] : $x;
if (!$htmlspecialcharsBypass) {
$title = htmlspecialchars($title);
}
$options .= cot_rc($rc, [
'value' => $htmlspecialcharsBypass ? $x : htmlspecialchars($x),
'selected' => $selected,
'title' => $title
]);
}
$rc = empty($R["input_select_{$rc_name}"]) ? (empty($custom_rc) ? 'input_select' : $custom_rc) : "input_select_{$rc_name}";
$result = cot_rc($rc, [
'name' => $name,
'attrs' => $input_attrs,
'error' => $error,
'options' => $options
]);
return $result;
}