Выбранные фильтры. Битрикс умный фильтр, catalog.smart.filter


html

<div class="smart_filter_checked_items" id="filterCheckedItemsBox"></div>

script.js

function JCSmartFilter(ajaxURL, viewMode, params)
{
    // ...
    this.filterContainer = document.querySelector('#catalogFilter')
    this.checkboxes = this.filterContainer.querySelectorAll('form input[type="checkbox"]');
    // Проверяем выбранные фильтры при загрузке.
    document.addEventListener('DOMContentLoaded', () => {
        this.filterMarkedItemsBox = document.querySelector('#filterCheckedItemsBox') ?? null;
        this.filterMarkedItemsOnLoad()
    });
}

/**
 * Рендерим кнопки при загрузке страницы.
 */
JCSmartFilter.prototype.filterMarkedItemsOnLoad = function() {
    if (this.checkboxes) {
        this.checkboxes.forEach(checkbox => {
            if (checkbox.checked) {
                this.filterMarkedItemsCheck(checkbox, true)
            }
        })
    }
}

/**
 * Проверяем есть ли уже кнопка в контейнере?
 */
JCSmartFilter.prototype.filterMarkedItemsCheck = function(checkbox, onLoad = false) {
    let existingButton = this.filterMarkedItemsBox.querySelector(`[data-filter-checked="${checkbox.id}"]`);

    if (onLoad) {
        if (!existingButton) {
            let newButton = this.filterMarkedItemsButtonCreate(checkbox);
            return this.filterMarkedItemsButtonRender(newButton);
        } else {
            existingButton.onclick = () => this.filterMarkedItemsButtonRemoveAndSubmit(existingButton)
            return existingButton
        }
    }

    if (checkbox.checked) {
        if (!existingButton) {
            let newButton = this.filterMarkedItemsButtonCreate(checkbox);
            this.filterMarkedItemsButtonRender(newButton);
        }
        return;
    }

    if (existingButton) {
        existingButton.remove();
    }
};

/**
 * Создаем кнопку.
 */
JCSmartFilter.prototype.filterMarkedItemsButtonCreate = function(checkbox) {
	let button = document.createElement('button');
	button.type = 'button';
	button.setAttribute('data-filter-checked', checkbox.id);

	let text = checkbox.dataset.labelText;
	let truncatedText = text;

	// Обрезаем текст, если длина больше N символов
	if (text.length > 15) {
		truncatedText = text.substring(0, 15) + '...';
	}

	button.textContent = truncatedText;
	button.title = text; // Полный текст в атрибут title

	return button;
};

JCSmartFilter.prototype.filterMarkedItemsButtonRender = function(newButton) {
    newButton.onclick = () => this.filterMarkedItemsButtonRemoveAndSubmit(newButton);

    if (this.filterMarkedItemsBox === null) {
        this.filterMarkedItemsBox = document.querySelector('#filterCheckedItemsBox')
    }
    this.filterMarkedItemsBox.appendChild(newButton)
};
/**
 * При клике по выбранным фильтрам убираем checked в фильтре.
 */
JCSmartFilter.prototype.filterMarkedItemsButtonRemoveAndSubmit = function(button) {
    let checkbox = this.filterContainer.querySelector(`#${button.dataset.filterChecked}`);

    if (checkbox) {
        checkbox.click()
    }
};

template.php

Добавляем чекбоксу в фильтре атрибут data-label-text

Можно забирать текст кнопки и из значения <span>, но зачастую просят в выбранные фильтры прописывать другие значения, поэтому так более удобно.

// ... код выше
default://CHECKBOXES
<input
    type="checkbox"
    value="<? echo $ar["HTML_VALUE"] ?>"
    data-label-text="<?=$ar["VALUE"]?>"
    data-seo-name="<?=$seoName?>"
/>

Если нужна перезагрузка

Если фильтр перезагружает страницу сразу (параметр "INSTANT_RELOAD" => "Y" ), то тут взятки гладки.

А если требуется перезагрузить страницу руками, то добавляем

  1. Флаг перезагрузки страницы в конструктор

    function JCSmartFilter(ajaxURL, viewMode, params)
    {
    // ...
    // флаг перезагрузки
    this.needReload = false;
    // Нода каталога (опционально, мне нужна чтобы навешивать loader)
    this.nodeCatalog = document.querySelector('[data-catalog]') ?? null;
    }
  2. В методе filterMarkedItemsButtonRemoveAndSubmit переключаем флаг

    if (checkbox) {
    this.needReload = true;
    checkbox.click()
    }
  3. Ловим this.needReload в функции лоадера

    JCSmartFilter.prototype.showLoaderForElement = function(action = 'add')
    {
    if (!this.nodeFilter || !this.nodeCatalog) {
        return;
    }
    
    const targetElement = this.needReload ? this.nodeCatalog : this.nodeFilter;
    
    toggleLoader(targetElement, action); // Ваша функция, которая навешивает loader
    };
  4. И отслеживаем в методе

    JCSmartFilter.prototype.postHandler = function (result, fromCache)
    {
    
    // Если требуется перезагрузка - делаем это сразу
    if (this.needReload && result.FILTER_URL) {
        const href = BX.util.htmlspecialcharsback(result.FILTER_URL);
        window.location.href = href;
        return; // Прерываем выполнение остального кода
    }
    
    };

Если нужна перезагрузка 2

В методе выше мы отправляем ajax-запрос и формируем ссылку, так как мы это сделали, нажав на чекбокс фильтра. И это безопасный и правильный подход.

В частных случаях, когда это отрабатывает медленно (слабая конфигурация, нагроможденные фильтры и т.д.) можно пойти другим путем. Не отправлять ajax-запрос и не дожидаться его ответа.

Возможны баги, следует тестировать и допиливать конкретно под проект.

Приступаем!

template.php

Добавляем чекбоксу в фильтре атрибут data-filter-code

// ... код выше
default://CHECKBOXES
<input
    // ... другие атрибуты
    data-filter-code="<?= strtolower($arItem['CODE']) ?>"
/>

 

script.js

Изменяем метод filterMarkedItemsButtonRemoveAndSubmit Дополнительно получаем код и значение свойства.

JCSmartFilter.prototype.filterMarkedItemsButtonRemoveAndSubmit = function(button) {
    let checkbox = this.filterContainer.querySelector(`#${button.dataset.filterChecked}`);

    if (checkbox) {
        this.needReload = true;

        // Получаем код и значение фильтруемого свойства
        const filterCode = checkbox.dataset.filterCode;
        const filterValue = checkbox.dataset.labelText.toLowerCase();

        // Сразу перезагружаем без AJAX-запроса
        this.reloadPageDirectly(filterCode, filterValue);
    }
};

 

Дописываем остальные методы

/**
 * Прямая перезагрузка без AJAX
 */
JCSmartFilter.prototype.reloadPageDirectly = function(removeCode, removeValue) {
    this.showLoaderForElement();

    // Строим новый URL без удаляемого фильтра
    window.location.href = this.buildFilterUrl(removeCode, removeValue);
};

/**
 * Построение URL фильтра с удалением конкретного фильтра
 */
JCSmartFilter.prototype.buildFilterUrl = function(removeCode, removeValue) {
    let currentUrl = window.location.pathname;

    if (currentUrl.includes('/filter/')) {
        const urlParts = currentUrl.split('/filter/');
        const baseUrl = urlParts[0];

        if (urlParts.length > 1) {
            let filterPart = urlParts[1];
            filterPart = filterPart.replace(/\/apply\/?$/, '');
            const filters = filterPart.split('/').filter(part => part.length > 0);

            // Декодируем компоненты для сравнения
            const decodedFilters = filters.map(filter => decodeURIComponent(filter));
            const filterToRemove = `${removeCode}-is-${removeValue}`;

            // Создаем новый массив для отфильтрованных фильтров
            const resultFilters = [];

            for (let i = 0; i < filters.length; i++) {
                const filter = filters[i];
                const decodedFilter = decodedFilters[i];

                // Если это фильтр, который нужно полностью удалить
                if (decodedFilter === filterToRemove) {
                    continue; // Пропускаем этот фильтр
                }

                // Если фильтр содержит несколько значений через "or"
                if (decodedFilter.includes('-or-') && decodedFilter.startsWith(`${removeCode}-is-`)) {
                    // Разделяем фильтр на код и значения
                    const parts = decodedFilter.split('-is-');
                    if (parts.length < 2) {
                        resultFilters.push(filter);
                        continue;
                    }

                    const code = parts[0];
                    const values = parts[1].split('-or-');

                    // Удаляем нужное значение
                    const filteredValues = values.filter(value => value !== removeValue);

                    // Если значения остались, формируем новый фильтр
                    if (filteredValues.length > 0) {
                        // Кодируем значения обратно для URL
                        const encodedValues = filteredValues.map(value => encodeURIComponent(value));
                        resultFilters.push(`${code}-is-${encodedValues.join('-or-')}`);
                    }
                    // Если значений не осталось, не добавляем фильтр
                } else {
                    // Оставляем фильтр без изменений
                    resultFilters.push(filter);
                }
            }

            if (resultFilters.length > 0) {
                return `${baseUrl}/filter/${resultFilters.join('/')}/apply/`;
            } else {
                return `${baseUrl}/`;
            }
        }
    }

    // Если это обычный URL с параметрами, или что-то пошло не так
    return currentUrl;
};

 

Теперь фильтры удаляются минуя ajax-запрос, что в некоторых случаях работает в несколько раз быстрее.