Раскрывающиеся блоки свойств
Не путать с DROPDOWN.
Верстка (id, классы, data) максимально сохранена к шаблонному дефолтному варианту.
Кейс
-
По клику на свойство необходимо отобразить раскрывающийся блок.
-
При клике на другое свойство, текущий открытый блок должен закрыться.
-
При клике ВНЕ элементов фильтра, должны закрыться Все открытые блоки (если есть возможность открывать несколько блоков или один текущий).
Работаем с catalog.smart.filter/имя_шаблона/script.js
За открытие закрытие блока отвечает метод hideFilterProps
Анимация реализована через
new BX.easing({
duration : 10,
start : { opacity: 1, height: filterBlock.offsetHeight },
finish : { opacity: 0, height:0 },
// transition : BX.easing.transitions.quart, // Эффект анимации. https://dev.1c-bitrix.ru/api_help/js_lib/animation/animation.php
step : function(state){
filterBlock.style.opacity = state.opacity;
filterBlock.style.height = state.height + "px";
filterBlock.style.overflow = 'hidden'; // Иногда при закрытии блока, содержимое вываливается за пределы. Исправляем.
},
complete : function() {
filterBlock.setAttribute("style", "");
BX.removeClass(obj, "bx-active");
}
}).animate();
При инициализации получим все блоки со свойствами, которые у нас есть и запустим функцию прослушки клика.
function JCSmartFilter(ajaxURL, viewMode, params)
{
// ...
this.filterItemsContainer = document.querySelector('.filter-items')
this.filterPropsDropdowns = this.filterItemsContainer.querySelectorAll('[data-role="bx_filter_block"]'); // Все блоки
this.listenClickOutsideFilterProps(); // Клик
// ...
}
hideFilterProps
JCSmartFilter.prototype.hideFilterProps = function(element)
{
var obj = element.parentNode,
filterBlock = obj.querySelector("[data-role='bx_filter_block']"),
propAngle = obj.querySelector("[data-role='prop_angle']");
this.closeOtherPropsDropdowns(filterBlock); // Наш метод, который закрывает все блоки, кроме того по которому кликнули.
if(BX.hasClass(obj, "bx-active"))
{
new BX.easing({
duration : 10,
start : { opacity: 1, height: filterBlock.offsetHeight },
finish : { opacity: 0, height:0 },
// transition : BX.easing.transitions.quart,
step : function(state){
filterBlock.style.opacity = state.opacity;
filterBlock.style.height = state.height + "px";
filterBlock.style.overflow = 'hidden';
},
complete : function() {
filterBlock.setAttribute("style", "");
BX.removeClass(obj, "bx-active");
}
}).animate();
BX.addClass(propAngle, "fa-angle-down");
BX.removeClass(propAngle, "fa-angle-up");
}
else
{
filterBlock.style.display = "block";
filterBlock.style.opacity = 0;
filterBlock.style.height = "auto";
var obj_children_height = filterBlock.offsetHeight;
filterBlock.style.height = 0;
new BX.easing({
duration : 10,
start : { opacity: 0, height: 0 },
finish : { opacity: 1, height: obj_children_height },
// transition : BX.easing.transitions.quart,
step : function(state){
filterBlock.style.opacity = state.opacity;
filterBlock.style.height = state.height + "px";
},
complete : function() {
}
}).animate();
BX.addClass(obj, "bx-active");
BX.removeClass(propAngle, "fa-angle-down");
BX.addClass(propAngle, "fa-angle-up");
}
};
listenClickOutsideFilterProps
/**
* Отслеживает клики ВНЕ блоков свойств фильтра.
* Стартует событие из инициализации.
*/
JCSmartFilter.prototype.listenClickOutsideFilterProps = function()
{
if(this.filterItemsContainer) {
if(this.filterPropsDropdowns.length > 0) {
document.addEventListener('click', (e) => {
if (!e.composedPath().includes(this.filterItemsContainer)) {
this.closeAllPropsDropdowns()
}
})
}
}
};
closeOtherPropsDropdowns
/**
* Закрывает все блоки свойств фильтра КРОМЕ того, который открыли.
*/
JCSmartFilter.prototype.closeOtherPropsDropdowns = function(filterBlock)
{
if(this.filterPropsDropdowns.length > 1) {
let allFilterBlocks = Array.from(this.filterPropsDropdowns);
let otherFilterBlocks = allFilterBlocks.filter(item => item !== filterBlock)
otherFilterBlocks.forEach(otherFilterBlock => {
this.closePropDropdown(otherFilterBlock)
})
} else {
}
};
closeAllPropsDropdowns
/**
* Закрывает все блоки свойств фильтра.
*/
JCSmartFilter.prototype.closeAllPropsDropdowns = function()
{
// Наличие this.filterPropsDropdowns проверили Выше.
this.filterPropsDropdowns.forEach(filterBlock => {
this.closePropDropdown(filterBlock)
})
};
closeOtherPropsDropdowns
/**
* Закрывает все блоки свойств фильтра КРОМЕ того, который открыли.
*/
JCSmartFilter.prototype.closeOtherPropsDropdowns = function(filterBlock)
{
if(this.filterPropsDropdowns.length > 1) {
let allFilterBlocks = Array.from(this.filterPropsDropdowns);
let otherFilterBlocks = allFilterBlocks.filter(item => item !== filterBlock)
otherFilterBlocks.forEach(otherFilterBlock => {
this.closePropDropdown(otherFilterBlock)
})
} else {
}
};
closePropDropdown
/**
* Закрывает один блок свойств фильтра.
* Принимает filterBlock = div.data-role="bx_filter_block"
*/
JCSmartFilter.prototype.closePropDropdown = function(filterBlock)
{
var obj = filterBlock.parentNode,
propAngle = obj.querySelector("[data-role='prop_angle']");
new BX.easing({
duration : 10,
start : { opacity: 1, height: filterBlock.offsetHeight },
finish : { opacity: 0, height:0 },
// transition : BX.easing.transitions.quart,
step : function(state){
filterBlock.style.opacity = state.opacity;
filterBlock.style.height = state.height + "px";
filterBlock.style.overflow = 'hidden';
},
complete : function() {
filterBlock.setAttribute("style", "");
BX.removeClass(obj, "bx-active");
}
}).animate();
BX.addClass(propAngle, "fa-angle-down");
BX.removeClass(propAngle, "fa-angle-up");
};
Поиск по буквам в фильтре
catalog.smart.filter/имя_шаблона/script.js
/**
* Поиск по буквам в фильтре.
*
* Обязательные атрибуты для поиска:
* * data-search-item - контейнер с элементом.
* * data-search-value - контейнер со значением, который ищем.
*/
JCSmartFilter.prototype.searchByLetters = function (element) {
// Объявлять переменные.
let input, filter, ul, li, a, i, txtValue;
input = element
filter = input.value.toUpperCase();
ul = element.parentElement
li = ul.querySelectorAll('[data-search-item]')
// Скрываем все, что не соответствует запросу.
for (i = 0; i < li.length; i++) {
a = li[i].querySelector('[data-search-value]');
txtValue = a.textContent || a.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}
};
html
<div class="bx-filter-parameters-box bx-active">
<span class="bx-filter-container-modef"></span>
<div class="bx-filter-parameters-box-title" onclick="smartFilter.hideFilterProps(this)">
<span class="bx-filter-parameters-box-hint">Входное напряжение, V
<i data-role="prop_angle" class="fa fa-angle-down"></i>
</span>
</div>
<div class="bx-filter-block" data-role="bx_filter_block" style="">
<div class="bx-filter-parameters-box-container">
<input class="filter_search" type="text" autocomplete="off" placeholder="Быстрый поиск"
onkeyup="smartFilter.searchByLetters(this)" wfd-id="id23">
<div class="checkbox" data-search-item="">
<label data-role="label_arrFilter_884_2537871056" class="bx-filter-param-label "
for="arrFilter_884_2537871056">
<input type="checkbox" value="Y" name="arrFilter_884_2537871056" id="arrFilter_884_2537871056"
onclick="smartFilter.click(this)" wfd-id="id24">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="AC 220 V">AC 220 V</span>
</label>
</div>
<div class="checkbox" data-search-item="">
<label data-role="label_arrFilter_884_3929219733" class="bx-filter-param-label "
for="arrFilter_884_3929219733">
<input type="checkbox" value="Y" name="arrFilter_884_3929219733" id="arrFilter_884_3929219733"
onclick="smartFilter.click(this)" wfd-id="id25">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="DC 5 V">DC 5 V</span>
</label>
</div>
<div class="checkbox" data-search-item="">
<label data-role="label_arrFilter_884_2227739503" class="bx-filter-param-label "
for="arrFilter_884_2227739503">
<input type="checkbox" value="Y" name="arrFilter_884_2227739503" id="arrFilter_884_2227739503"
onclick="smartFilter.click(this)" wfd-id="id26">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="AC 12 V">AC 12 V</span>
</label>
</div>
<div class="checkbox" data-search-item="">
<label data-role="label_arrFilter_884_2208653174" class="bx-filter-param-label "
for="arrFilter_884_2208653174">
<input type="checkbox" value="Y" name="arrFilter_884_2208653174" id="arrFilter_884_2208653174"
onclick="smartFilter.click(this)" wfd-id="id27">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="DC 12 V">DC 12 V</span>
</label>
</div>
<div class="checkbox" data-search-item="">
<label data-role="label_arrFilter_884_4104286176" class="bx-filter-param-label "
for="arrFilter_884_4104286176">
<input type="checkbox" value="Y" name="arrFilter_884_4104286176" id="arrFilter_884_4104286176"
onclick="smartFilter.click(this)" wfd-id="id28">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="DC 24 V">DC 24 V</span>
</label>
</div>
<div class="checkbox _hidden _isShown" data-search-item="">
<label data-role="label_arrFilter_884_1679651441" class="bx-filter-param-label "
for="arrFilter_884_1679651441">
<input type="checkbox" value="Y" name="arrFilter_884_1679651441" id="arrFilter_884_1679651441"
onclick="smartFilter.click(this)" wfd-id="id29">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="DC 220 V">DC 220 V</span>
</label>
</div>
<div class="checkbox _hidden _isShown" data-search-item="">
<label data-role="label_arrFilter_884_1943915266" class="bx-filter-param-label "
for="arrFilter_884_1943915266">
<input type="checkbox" value="Y" name="arrFilter_884_1943915266" id="arrFilter_884_1943915266"
onclick="smartFilter.click(this)" wfd-id="id30">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="12">12</span>
</label>
</div>
<div class="checkbox _hidden _isShown" data-search-item="">
<label data-role="label_arrFilter_884_3939801784" class="bx-filter-param-label "
for="arrFilter_884_3939801784">
<input type="checkbox" value="Y" name="arrFilter_884_3939801784" id="arrFilter_884_3939801784"
onclick="smartFilter.click(this)" wfd-id="id31">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="5">5</span>
</label>
</div>
<div class="checkbox _hidden _isShown" data-search-item="">
<label data-role="label_arrFilter_884_167867302" class="bx-filter-param-label "
for="arrFilter_884_167867302">
<input type="checkbox" value="Y" name="arrFilter_884_167867302" id="arrFilter_884_167867302"
onclick="smartFilter.click(this)" wfd-id="id32">
<span class="ui_checkbox"></span>
<span class="bx-filter-param-text" data-search-value="" title="24">24</span>
</label>
</div>
</div>
<a class="show_all _active" onclick="smartFilter.showAll(this)" href="javascript:void(0);" rel="nofollow">
Показать все <i data-role="prop_angle"></i>
</a>
</div>
</div>
Показываем скрытые элементы при клике на кнопку "Показать еще".
/**
* Показываем скрытые элементы при клике на кнопку "Показать еще".
*/
JCSmartFilter.prototype.showAll = function (element) {
let parent = element.parentElement
let hiddenElements = parent.querySelectorAll('._hidden')
if (element.classList.contains('_active')) {
this.showAllClose(hiddenElements, element, parent)
} else {
this.showAllOpen(hiddenElements, element, parent)
}
};
JCSmartFilter.prototype.showAllOpen = function (elements, button, parent) {
parent.setAttribute('style', '')
elements.forEach(el => {
el.classList.add('_isShown')
})
button.classList.add('_active')
}
JCSmartFilter.prototype.showAllClose = function (elements, button, parent) {
parent.setAttribute('style', '')
elements.forEach(el => {
el.classList.remove('_isShown')
})
button.classList.remove('_active')
}
Еще вариант
JCSmartFilter.prototype.showMore = function(button) {
let container = button.parentNode;
let elements = container.querySelectorAll('.checkbox');
let isShowing = button.textContent === 'Показать еще';
elements.forEach(element => {
if (element.classList.contains('d-none') || element.classList.contains('show')) {
if (isShowing) {
element.classList.remove('d-none');
element.classList.add('show');
} else {
element.classList.remove('show');
element.classList.add('d-none');
}
}
});
button.textContent = isShowing ? 'Скрыть' : 'Показать еще';
}; <div onclick="JCSmartFilter.prototype.showMore(this)">Показать еще</div>
template.php
default://CHECKBOXES
?>
<div class="col-xs-12">
<? $i = 1;
foreach($arItem["VALUES"] as $val => $ar){?>
<div class="checkbox<?= $i > 7 ? ' d-none' : '' ?>">
<label data-role="label_<?=$ar["CONTROL_ID"]?>" class="bx-filter-param-label <? echo $ar["DISABLED"] ? 'disabled': '' ?>" for="<? echo $ar["CONTROL_ID"] ?>">
<span class="bx-filter-input-checkbox">
<input
type="checkbox"
value="<? echo $ar["HTML_VALUE"] ?>"
name="<? echo $ar["CONTROL_NAME"] ?>"
id="<? echo $ar["CONTROL_ID"] ?>"
<? echo $ar["CHECKED"]? 'checked="checked"': '' ?>
onclick="smartFilter.click(this)"
data-label-text="<?=$ar["VALUE"]?>"
/>
<span class="bx-filter-param-text" title="<?=$ar["VALUE"];?>"><?=$ar["VALUE"];?><?
if ($arParams["DISPLAY_ELEMENT_COUNT"] !== "N" && isset($ar["ELEMENT_COUNT"])):
?> (<span data-role="count_<?=$ar["CONTROL_ID"]?>"><? echo $ar["ELEMENT_COUNT"]; ?></span>)<?
endif;?></span>
</span>
</label>
</div>
<? $i++;
} ?>
<? if($i > 7) { ?>
<div onclick="JCSmartFilter.prototype.showMore(this)">Показать еще</div>
<? } ?>
</div>
<?
}
?>
Прячем лейбл с результатами фильтра через N сек
/**
* Прячем лейбл с результатами фильтра.
*/
JCSmartFilter.prototype.hidePopupResult = function (modef) {
setTimeout(() => {
if (modef.style.display === 'inline-block') {
modef.style.display = 'none';
}
}, 5000)
}
Вызываем в postHandler
Индикатор выбранного фильтра
-
При выборе свойства отображается индикатор.
-
При загрузке страницы отображается индикатор.
-
У примененного свойства (текущего) индикатор не убирается пока не сбросить фильтр
template.php
<div class="filter-item bx-filter-parameters-box<?= $arItem["DISPLAY_EXPANDED"] === "Y" ? ' _has_checked _current_filter' : ''?>">
JS
JCSmartFilter.prototype.toggleFilterCheckedIndicator = function (checkbox) {
this.hasCheckedFilterProps(checkbox.closest('.bx-filter-parameters-box'))
};
JCSmartFilter.prototype.hasCheckedFilterProps = function (propContainer) {
let hasCheckedProps = propContainer.querySelectorAll('.bx-filter-parameters-box-container input:checked').length > 0;
let hasCurrentFilter = propContainer.classList.contains('_current_filter');
if (!hasCurrentFilter) {
propContainer.classList.toggle('_has_checked', hasCheckedProps);
}
}
Смотрим что отрабатывает по клику на checkbox.
Вызываем наш обработчик в click()
JCSmartFilter.prototype.click = function (checkbox) {
this.toggleFilterCheckedIndicator(checkbox);
if (!!this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(BX.delegate(function () {
this.reload(checkbox);
}, this), 500);
};