Поиск по буквам, фильтр по буквам

Простой код для реализации динамического поиска по элементам страницы. При вводе букв в input остаются только те элементы по которым есть вхождение подстроки.

Функция searchByLetters реализует универсальный динамический поиск и обработкой отсутствующих результатов.

 

<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Поиск имен..">

<ul id="myUL">
  <li><a href="#">Андрей</a></li>
  <li><a href="#">Алексей</a></li>

  <li><a href="#">Борис</a></li>
  <li><a href="#">Владимир</a></li>

  <li><a href="#">Сергей</a></li>
  <li><a href="#">Кристина</a></li>
  <li><a href="#">Татьяна</a></li>
</ul>
function myFunction() {
  // Объявлять переменные
  let input, filter, ul, li, a, i, txtValue;
  input = document.getElementById('myInput');
  filter = input.value.toUpperCase();
  ul = document.getElementById("myUL");
  li = ul.getElementsByTagName('li');

  // Выполните цикл по всем элементам списка и скройте те, которые не соответствуют запросу поиска
  for (i = 0; i < li.length; i++) {
    a = li[i].getElementsByTagName("a")[0];
    txtValue = a.textContent || a.innerText;
    if (txtValue.toUpperCase().indexOf(filter) > -1) {
      li[i].style.display = "";
    } else {
      li[i].style.display = "none";
    }
  }
}

Битрикс фильтр

function filterSearch(input) {
        let filter, wrapper, ul, li, a, i, txtValue;
        filter = input.value.toUpperCase();
        wrapper = input.closest('.l_search_wr');
        ul = wrapper.querySelector('.l_search_ul');
        li = ul.querySelectorAll('.l_search_li');

        for (i = 0; i < li.length; i++) {
            a = li[i].querySelector(".l_search_a");
            txtValue = a.textContent || a.innerText;
            if (txtValue.toUpperCase().indexOf(filter) > -1) {
                li[i].style.display = "";
            } else {
                li[i].style.display = "none";
            }
        }
    }
<? foreach ($arResult["ITEMS"] as $key => $arItem) {
                                    if (empty($arItem["VALUES"]) || isset($arItem["PRICE"])) {
                                        continue;
                                    }

                                    if (
                                        $arItem["DISPLAY_TYPE"] === SectionPropertyTable::NUMBERS_WITH_SLIDER
                                        && ($arItem["VALUES"]["MAX"]["VALUE"] - $arItem["VALUES"]["MIN"]["VALUE"] <= 0)
                                    ) {
                                        continue;
                                    }
                                    $hasSearch = false;

                                    if (count($arItem["VALUES"]) > 10) {
                                        $hasSearch = true;
                                    }
                                    ?>
                                    <div<?=$arItem['CODE'] === 'ATTR_FORM_MONUMENT'
                                    || $arItem['CODE'] === 'ATTR_ELEMENTS_MONUMENT' ? ' data-filter-hidden' : ''?>
                                        class="filter-item-r bx-filter-parameters-box-r bx-filter-parameters-box bx-active">
                                        <span class="bx-filter-container-modef"></span>
                                        <div class="bx-filter-parameters-box-title-r">
                                            <?=$arItem["NAME"]?>
                                        </div>
                                        <div class="bx-filter-block-r _scrollbar<?=$hasSearch ? ' l_search_wr' : ''?>"
                                             data-role="bx_filter_block">
                                            <? if (count($arItem["VALUES"]) > 10) { ?>
                                                <div class="bx_filter_search">
                                                    <input type="text" placeholder="Поиск..."
                                                           onkeyup="filterSearch(this)">
                                                </div>
                                            <? } ?>
                                            <div class="bx-filter-parameters-box-container-r<?=$hasSearch
                                                ? ' l_search_ul' : ''?>">
                                                <?
                                                $arCur = current($arItem["VALUES"]);
                                                switch ($arItem["DISPLAY_TYPE"]) {

                                                default://CHECKBOXES
                                                ?>
                                                <? foreach ($arItem["VALUES"] as $val => $ar): ?>
                                                    <div class="checkbox<?=$hasSearch ? ' l_search_li' : ''?>">
                                                        <label data-role="label_<?=$ar["CONTROL_ID"]?>"
                                                               class="bx-filter-param-label bx-filter-param-label-r <? echo $ar["DISABLED"]
                                                                   ? 'disabled'
                                                                   : '' ?>" for="<? echo $ar["CONTROL_ID"] . '-MOB' ?>">
                                                                <span class="bx-filter-input-checkbox">
                                                                    <input
                                                                        type="checkbox"
                                                                        value="<? echo $ar["HTML_VALUE"] ?>"
                                                                        name="<? echo $ar["CONTROL_NAME"] ?>"
                                                                        id="<? echo $ar["CONTROL_ID"] . '-MOB' ?>"
                                                                        <? echo $ar["CHECKED"] ? 'checked="checked"'
                                                                            : '' ?>
                                                                        onclick="smartFilter.click(this)"
                                                                    />
                                                                    <span
                                                                        class="bx-filter-param-text bx-filter-param-text-r<?=$hasSearch
                                                                            ? ' l_search_a' : ''?>"><?=$ar["VALUE"]?><?
                                                                        if (
                                                                            $arParams["DISPLAY_ELEMENT_COUNT"] !== "N"
                                                                            && isset($ar["ELEMENT_COUNT"])
                                                                        ):?>&nbsp;(
                                                                            <span
                                                                                data-role="count_<?=$ar["CONTROL_ID"]?>"><? echo $ar["ELEMENT_COUNT"]; ?></span>)<?
                                                                        endif; ?>
                                                                    </span>
                                                                </span>
                                                        </label>
                                                    </div>
                                                <? endforeach; ?>
                                                <?
                                                }
                                                ?>
                                            </div>
                                        </div>
                                    </div>
                                <? } ?>

Более универсальный код

1. Поиск и фильтрация элементов

  • Цель: Фильтрует элементы внутри указанного контейнера на основе введенного текста.

  • Как работает:

    • Ищет элементы с атрибутом data-letterSearch="li" (элементы списка).

    • Для каждого элемента проверяет все поля с атрибутом data-letterSearch="field" (поля для поиска).

    • Сравнивает текст полей с введенным значением (регистронезависимо).

2. Управление отображением

  • Найденные элементы:

    • Плавно появляются с анимацией (opacity и transform).

    • Используют CSS-класс d-none для управления видимостью.

  • Не найденные элементы:

    • Скрываются с задержкой для анимации (350 мс).

    • Добавляется CSS-класс d-none.

3. Динамическое уведомление "Ничего не найдено"

  • Логика:

    • Если совпадений нет и поле поиска не пустое — показывает сообщение.

    • Если есть результаты или поле пустое — скрывает сообщение.

  • Особенности:

    • Элемент уведомления создается динамически при первом вызове.

    • Анимируется через CSS-свойство opacity.

4. Архитектурные особенности

  • Универсальность:

    • Работает с любым контейнером через атрибут data-letterSearch-id.

    • Не зависит от структуры HTML (кроме указанных data-атрибутов).

Пример использования:

<input onkeyup="searchByLetters(this, 'myContainer')">
<div data-letterSearch-id="myContainer">
  <div data-letterSearch="li">
    <div data-letterSearch="field">Текст для поиска</div>
  </div>
</div>

function searchByLetters(input, containerId) {

    let filter, container, items, fields, i, txtValue;
    filter = input.value.toUpperCase();

    container = document.querySelector(`[data-letterSearch-id="${containerId}"]`);
    if (!container) return;

    items = container.querySelectorAll('[data-letterSearch="li"]');

    for (i = 0; i < items.length; i++) {
        fields = items[i].querySelectorAll('[data-letterSearch="field"]');
        let found = false;

        for (let j = 0; j < fields.length; j++) {
            txtValue = fields[j].textContent || fields[j].innerText;
            if (txtValue.toUpperCase().indexOf(filter) > -1) {
                found = true;
                break;
            }
        }

        items[i].style.display = found ? "" : "none";
    }
}
<div class="product-block">
    <div class="b-title"><h3><?=$arResult['PROPERTIES']['PALETTE']['NAME']?></h3></div>
    <div class="b-search">
        <div class="ui-field-container palette-search">
            <label for="paletteSearch">Поиск по коду:</label>
            <input class="form-control" type="text" id="paletteSearch"
                   onkeyup="searchByLetters(this, 'palette')">
        </div>
    </div>
    <div class="product-palette">
        <div class="palette-grid" data-letterSearch="ul" data-letterSearch-id="palette">
            <? foreach ($arResult['PROPERTIES']['PALETTE']['VALUE'] as $k => $fileId) {
                $src = CFile::GetPath($fileId);
                $desc = $arResult['PROPERTIES']['PALETTE']['DESCRIPTION'][$k];
                ?>
                <div class="palette-grid__item" data-letterSearch="li">
                    <div class="palette-item__top">
                        <a class="palette-image" href="<?=$src?>" data-fancybox="palette">
                            <img src="<?=$src?>" alt="<?=$desc?>">
                        </a>
                    </div>
                    <div class="palette-item__bot">
                        <div class="palette-title copy-click dotted" data-letterSearch="field">
                            <?=$desc?>
                        </div>
                    </div>
                </div>
            <? } ?>
        </div>
    </div>
</div>

Если нужно добавить плавное скрытие / появление.

function searchByLetters(input, containerId) {
    let filter, container, items, fields, i, txtValue;
    filter = input.value.toUpperCase();

    container = document.querySelector(`[data-letterSearch-id="${containerId}"]`);
    if (!container) return;

    items = container.querySelectorAll('[data-letterSearch="li"]');

    // Проверим, есть ли уже элемент "Ничего не найдено".
    let notFoundEl = container.querySelector('.search-not-found');
    if (!notFoundEl) {
        // Создадим элемент для сообщения "Ничего не найдено"
        notFoundEl = document.createElement('div');
        notFoundEl.className = 'search-not-found';
        notFoundEl.textContent = 'Ничего не найдено';
        notFoundEl.style.display = 'none';
        notFoundEl.style.textAlign = 'center';
        notFoundEl.style.padding = '20px 0';
        notFoundEl.style.color = 'var(--color-gray-600)';
        container.appendChild(notFoundEl);
    }

    let foundItems = 0;

    for (i = 0; i < items.length; i++) {
        fields = items[i].querySelectorAll('[data-letterSearch="field"]');
        let found = false;

        for (let j = 0; j < fields.length; j++) {
            txtValue = fields[j].textContent || fields[j].innerText;
            if (txtValue.toUpperCase().indexOf(filter) > -1) {
                found = true;
                break;
            }
        }

        if (found) {
            foundItems++;

            if (items[i].classList.contains('d-none')) {
                items[i].classList.remove('d-none');
            }

            setTimeout(() => {
                items[i].style.opacity = '1';
                items[i].style.transform = 'scale(1)';
            }, 50);

        } else {
            items[i].classList.add('d-none');

            // Задержка скрытия элемента до завершения анимации.
            setTimeout(() => {
                items[i].classList.add('d-none');
            }, 350); // Время должно быть равно или больше времени transition
        }
    }

    // Если строка поиска не пустая и ничего не найдено, показываем сообщение.
    if (filter && foundItems === 0) {
        // Покажем сообщение
        notFoundEl.style.display = 'block';
        // Анимация появления сообщения.
        setTimeout(() => {
            notFoundEl.style.opacity = '1';
        }, 50);
    } else {
        // Скроем сообщение.
        notFoundEl.style.opacity = '0';
        setTimeout(() => {
            notFoundEl.style.display = 'none';
        }, 350);
    }
}
/* Search by letters */
.search-not-found {
    transition: opacity var(--tr35);
    opacity: 0;
    font-size: 16px;
    margin-top: 15px;
}

[data-letterSearch="li"] {
    transition: opacity var(--tr35), transform var(--tr35);
    opacity: 1;
    transform: scale(1);
    height: auto;
    overflow: hidden;
}

.d-none {
    display: none;
}
// HTML см. выше.