Универсальный селект с тегами и динамическим отображением в одну строку
- Показывать справа: 0
Эта статья посвящен созданию универсального компонента селекта, который позволяет выбирать элементы из списка и отображать их в виде тегов. Если количество выбранных тегов превышает доступное пространство, лишние элементы скрываются под общим счетчиком. Этот компонент идеально подходит для фильтрации, выбора опций и других задач, где требуется гибкое взаимодействие с пользователем.
Шаг 1: HTML-структура
Создадим базовую HTML-разметку для нашего селекта. Мы будем использовать контейнеры, скрытые поля для хранения данных и элементы для отображения выбранных тегов.
<div class="webalan__products">
<div class="universal-tag-selector">
<label>Выберите опции:</label>
<div class="tag-selector-container">
<!-- Скрытое поле для хранения выбранных значений -->
<input type="hidden" name="selected_options" id="selected_options_hidden">
<!-- Контейнер для отображения выбранных тегов -->
<span class="selected-tags" id="selected-tags-display">
<!-- Здесь будут отображаться выбранные теги -->
</span>
<!-- Кнопка для открытия выпадающего списка -->
<button type="button" class="dropdown-toggle">
<span id="selected-count">+0</span>
<span uk-icon="triangle-down"></span> <!-- Иконка из UIkit -->
</button>
<!-- Выпадающий список с опциями -->
<div uk-dropdown="mode: click; boundary: ! .tag-selector-container; shift: false; flip: false">
<ul class="uk-nav uk-dropdown-nav" id="options-list">
<!-- Опции будут добавлены динамически или статически -->
<li><a href="#" data-value="option1">Опция 1</a></li>
<li><a href="#" data-value="option2">Опция 2</a></li>
<li><a href="#" data-value="option3">Опция 3</a></li>
<li><a href="#" data-value="option4">Опция 4</a></li>
<li><a href="#" data-value="option5">Опция 5</a></li>
</ul>
</div>
</div>
</div>
</div>
Пояснения к HTML:
.universal-tag-selector: Общий контейнер для компонента..tag-selector-container: Основной контейнер, который управляет отображением тегов и позиционированием кнопки.input[type="hidden"]#selected_options_hidden: Скрытое поле, куда будут записываться выбранные значения через запятую.#selected-tags-display: Элемент, который будет содержать видимые выбранные теги..dropdown-toggle: Кнопка, открывающая выпадающий список.#selected-count: Отображает количество выбранных элементов.[uk-dropdown]: Директив UIkit для создания выпадающего меню.#options-list: Список элементов в выпадающем меню. Атрибутdata-valueиспользуется для идентификации опции.
Шаг 2: JavaScript для интерактивности
JavaScript отвечает за всю логику: выбор/отмену выбора, обновление отображения тегов, проверку вместимости и обновление скрытого поля. Мы будем использовать jQuery для удобства.
// Вспомогательная функция для проверки наличия элемента в массиве
function in_array(needle, haystack, strict) {
var found = false, key, strict = !!strict;
for (key in haystack) {
if ((strict && haystack[key] === needle) || (!strict && haystack[key] == needle)) {
found = true;
break;
}
}
return found;
}
jQuery(document).ready(function(){
let selectedValues = []; // Массив для хранения выбранных значений
const tagContainer = $('.tag-selector-container');
const selectedTagsDisplay = $('#selected-tags-display');
const optionsList = $('#options-list');
const hiddenInput = $('#selected_options_hidden');
const selectedCountDisplay = $('#selected-count');
// Функция для обновления отображения выбранных тегов
function updateDisplay() {
selectedTagsDisplay.empty(); // Очищаем текущие отображаемые теги
const currentValues = selectedValues.slice(); // Создаем копию массива
// Если ничего не выбрано, показываем "Все опции" (или аналогичный текст)
if (currentValues.length === 0) {
selectedTagsDisplay.append($('Все опции'));
selectedCountDisplay.text("+0");
hiddenInput.val('');
return;
}
let totalWidth = 0;
let visibleTagsCount = 0;
let allVisible = true;
// Итерируем по выбранным значениям, чтобы определить, какие поместятся
for (let i = 0; i < currentValues.length; i++) {
const value = currentValues[i];
// Находим текст опции по значению
const optionText = optionsList.find(`a[data-value="${value}"]`).text();
const tagElement = $(`${optionText}`);
selectedTagsDisplay.append(tagElement);
// Проверяем, помещается ли тег в контейнер (учитывая ширину кнопки)
const tagWidth = tagElement.outerWidth(true);
totalWidth += tagWidth;
// 110 - это примерная ширина кнопки и отступов. Требуется тонкая настройка.
if (totalWidth > tagContainer.width() - 110) {
allVisible = false;
tagElement.remove(); // Удаляем тег, который не поместился
break;
}
visibleTagsCount++;
}
// Если не все теги поместились, добавляем индикатор "еще X"
if (!allVisible) {
const remainingCount = currentValues.length - visibleTagsCount;
const remainingElement = $(``);
selectedTagsDisplay.append(remainingElement);
}
// Обновляем счетчик выбранных элементов
selectedCountDisplay.text(`+${selectedValues.length}`);
// Обновляем скрытое поле
hiddenInput.val(selectedValues.join(','));
}
// Инициализация: обновляем отображение при загрузке страницы
updateDisplay();
// Обработчик клика по элементам в выпадающем списке
optionsList.on('click', 'a', function(event) {
event.preventDefault();
const value = $(this).data('value');
const $this = $(this);
if (selectedValues.includes(value)) {
// Удаляем значение, если оно уже выбрано
selectedValues = selectedValues.filter(v => v !== value);
$this.removeClass('selected');
} else {
// Добавляем значение, если оно не выбрано
selectedValues.push(value);
$this.addClass('selected');
}
updateDisplay(); // Обновляем отображение
});
// Обработчик клика по кнопке удаления тега
selectedTagsDisplay.on('click', '.remove-tag', function(e) {
e.preventDefault();
const $tagItem = $(this).closest('.tag-item');
const tagText = $tagItem.contents().filter(function() { return this.nodeType === 3; })[0].nodeValue.trim(); // Получаем текст тега
// Находим значение опции по тексту
const valueToRemove = optionsList.find('a').filter(function() {
return $(this).text().trim() === tagText;
}).data('value');
if (valueToRemove) {
selectedValues = selectedValues.filter(v => v !== valueToRemove);
// Снимаем класс 'selected' с элемента в выпадающем списке
optionsList.find(`a[data-value="${valueToRemove}"]`).removeClass('selected');
updateDisplay();
}
});
// Обработчик для открытия/закрытия дропдауна при клике на контейнер (опционально, если UIkit не справляется)
tagContainer.on('click', function(e) {
// Если клик был не по кнопке дропдауна и не по элементам внутри него
if (!$(e.target).closest('.uk-dropdown').length && !$(e.target).closest('.dropdown-toggle').length) {
$('.dropdown-toggle').trigger('click'); // Имитируем клик по кнопке для открытия/закрытия
}
});
});
Пояснения к JavaScript:
selectedValues: Массив хранит выбранные значения (например, `['option1', 'option3']`).updateDisplay():- Очищает
#selected-tags-display. - Создает элементы тегов для каждого выбранного значения.
- Динамически проверяет, помещаются ли созданные теги в
.tag-selector-container. - Если теги не помещаются, добавляет элемент
.remaining-tagsс количеством скрытых тегов. - Обновляет счетчик
#selected-countи скрытое поле#selected_options_hidden.
- Очищает
- Обработчик клика по
#options-list a:- Добавляет или удаляет значение из
selectedValues. - Переключает класс
.selectedдля визуального выделения опции в списке. - Вызывает
updateDisplay().
- Добавляет или удаляет значение из
- Обработчик клика по
.remove-tag:- Находит соответствующее значение в
selectedValues. - Удаляет его.
- Снимает класс
.selectedс опции в выпадающем списке. - Вызывает
updateDisplay().
- Находит соответствующее значение в
- Обработчик клика на
.tag-selector-container: Позволяет открыть/закрыть дропдаун, кликнув вне самой кнопки.
Шаг 3: Подключение стилей и UIkit
Стили для нашего скрипта:
<style>
/* Множественный выбор */
#tag-dropdown-list a.selected-tag {
background-color: #f0506e;
color: white;
border-radius: 5px;
}
#tag-dropdown-list a {
display: block;
/* padding: 3px 9px; */
}
#tag-dropdown-list li
{
margin-bottom: 5px;
}
#tag-dropdown-list input[type="checkbox"] {
display: none;
}
.webalan__products .tag-selector-container {
display: flex;
flex-wrap: wrap;
/* border: 1px solid #e5e5e5; */
padding: 5px;
border-radius: 0;
min-height: 37px;
border-bottom: 1px solid #333;
}
.webalan__products .tag-selector-container button {
flex-shrink: 0; padding: 0 10px;
max-height: 39px;
}
.webalan__products button .uk-icon{
position: relative;
top: 0px;
}
#tag-count {
display: none;
}
.webalan__products .uk-label.all__noselect {
border-radius: 5px;
padding: 5px 10px;
border: 0px solid #ccc;
background: #3c7be0;
color: rgb(255 255 255);
margin: 0 auto;
font-size: 14px;
font-weight: 500;
}
.webalan__products .selected-tags{
display: flex; flex-wrap: wrap; align-items: center; margin-right: 5px;
width: 100%;
}
.webalan__products .select_tags{
cursor: pointer;
}
.webalan__products .select_tags button{
margin-left: auto;
position: absolute;
right: 5px;
margin: 0px !important;
border-radius: 8px;
}
.webalan__products .select_tags
.uk-drop.uk-dropdown.uk-open
{
left: 0!important;
width: 100%;
}
.webalan__products .uk-margin-small-right.select_tags_carcas {
margin-right: 3px !important;
}
.webalan__products .select_tags_carcas {
display: inline-block; white-space: nowrap; background-color: #f0506e; color: white; border-radius: 5px; padding: 5px 10px;
}
.webalan__products .stop__right_not {
border-radius: 5px; padding: 5px 10px; border: 1px solid #ccc;
}
/*==============*/
</style>
Для работы данного кода необходимо подключить библиотеку UIkit. Используйте CDN:
<!-- Подключение CSS UIkit -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/css/uikit.min.css" />
<!-- Подключение JavaScript UIkit (иконок) -->
<script src="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/js/uikit-icons.min.js"></script>
<!-- Подключение jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Поместите эти строки перед закрывающим тегом </body>.
Заключение
Мы создали универсальный компонент селекта с тегами, который динамически управляет отображением выбранных элементов. Он адаптируется к доступному пространству, показывая лишь часть тегов и индикатор скрытых. Этот паттерн очень полезен для улучшения UX в различных сценариях веб-разработки.