Смарт-фильтр редизайн
Задача
Применить к умному фильтру битрикса свою верстку.
Решение
Описание принципов работы
Стандартный шаблон компонента catalog.smart.filter очень тесно завязан на JS и имеющейся разметке. Ниже будет представлен минимальный код, который обеспечит нам корректную работу умного фильтра и даст возможность писать свою разметку. Но наша разметка, к сожалению, так же будет тесно связана с javascript кодом, плюсом является применение jQuery, в котором проще разобраться.
Главная задача нашего кода - получить правельно сформированную ссылку умного фильтра. На основе этой ссылки при загрузке страницы компонент отфильтрует нам корректное количество элементов каталога.
Пример файла template.php
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
/** @var array $arParams */
/** @var array $arResult */
/** @global CMain $APPLICATION */
/** @global CUser $USER */
/** @global CDatabase $DB */
/** @var CBitrixComponentTemplate $this */
/** @var string $templateName */
/** @var string $templateFile */
/** @var string $templateFolder */
/** @var string $componentPath */
/** @var CBitrixComponent $component */
$this->setFrameMode(true);
$this->addExternalJs($templateFolder . '/js/partSmartFilter.js');
?>
<div class="catalog-filter">
<div class="cb-outer">
<input type="checkbox" id="cd_1" class="c-checkbox">
<label for="cd_1" class="c-checkbox__label">Акции</label>
</div>
<div class="cb-outer">
<input type="checkbox" id="cd_2" class="c-checkbox">
<label for="cd_2" class="c-checkbox__label">Товары партнёров</label>
</div>
<form name="<?echo $arResult["FILTER_NAME"]."_form"?>" action="<?echo $arResult["FORM_ACTION"]?>" method="get" class="smartfilter">
<?foreach($arResult["HIDDEN"] as $arItem):?>
<input type="hidden" name="<?echo $arItem["CONTROL_NAME"]?>" id="<?echo $arItem["CONTROL_ID"]?>" value="<?echo $arItem["HTML_VALUE"]?>" />
<?endforeach;?>
<? // PRICES
foreach($arResult["ITEMS"] as $key=>$arItem) :
$key = $arItem["ENCODED_ID"];
if(isset($arItem["PRICE"])):
if ($arItem["VALUES"]["MAX"]["VALUE"] - $arItem["VALUES"]["MIN"]["VALUE"] <= 0)
continue;
$step_num = 4;
$step = ($arItem["VALUES"]["MAX"]["VALUE"] - $arItem["VALUES"]["MIN"]["VALUE"]) / $step_num;
$prices = array();
if (Bitrix\Main\Loader::includeModule("currency"))
{
for ($i = 0; $i < $step_num; $i++)
{
$prices[$i] = CCurrencyLang::CurrencyFormat($arItem["VALUES"]["MIN"]["VALUE"] + $step*$i, $arItem["VALUES"]["MIN"]["CURRENCY"], false);
}
$prices[$step_num] = CCurrencyLang::CurrencyFormat($arItem["VALUES"]["MAX"]["VALUE"], $arItem["VALUES"]["MAX"]["CURRENCY"], false);
}
else
{
$precision = $arItem["DECIMALS"]? $arItem["DECIMALS"]: 0;
for ($i = 0; $i < $step_num; $i++)
{
$prices[$i] = number_format($arItem["VALUES"]["MIN"]["VALUE"] + $step*$i, $precision, ".", "");
}
$prices[$step_num] = number_format($arItem["VALUES"]["MAX"]["VALUE"], $precision, ".", "");
}?>
<div class="catalog-filter-section">
<div class="catalog-filter-section__title open">Цена</div>
<div class="param-list-wrp small-padd">
<div class="range-slider-inputs-wrp">
<input type="text"
class="minCost"
value="<?echo $arItem["VALUES"]["MIN"]["HTML_VALUE"] ? $arItem["VALUES"]["MIN"]["HTML_VALUE"] : $arItem["VALUES"]["MIN"]["VALUE"]?>"
name="<?echo $arItem["VALUES"]["MIN"]["CONTROL_NAME"]?>"
id="<?echo $arItem["VALUES"]["MIN"]["CONTROL_ID"]?>"
data-start-value="<?=$arItem["VALUES"]["MIN"]["VALUE"]?>"
/>
<span>-</span>
<input type="text"
class="maxCost"
name="<?echo $arItem["VALUES"]["MAX"]["CONTROL_NAME"]?>"
id="<?echo $arItem["VALUES"]["MAX"]["CONTROL_ID"]?>"
value="<?echo $arItem["VALUES"]["MAX"]["HTML_VALUE"] ? $arItem["VALUES"]["MAX"]["HTML_VALUE"] : $arItem["VALUES"]["MAX"]["VALUE"]?>"
data-start-value="<?=$arItem["VALUES"]["MAX"]["VALUE"]?>"
/>
</div>
<div id="range-slider"></div>
</div>
</div>
<? endif; ?>
<? endforeach; ?>
<?//not prices
foreach($arResult["ITEMS"] as $key=>$arItem) :
if ( empty($arItem["VALUES"])
|| isset($arItem["PRICE"])
) {
continue;
}
if ( $arItem["DISPLAY_TYPE"] == "A"
&& ($arItem["VALUES"]["MAX"]["VALUE"] - $arItem["VALUES"]["MIN"]["VALUE"] <= 0)
) {
continue;
}
?>
<div class="catalog-filter-section">
<div class="catalog-filter-section__title <?=($arItem["DISPLAY_EXPANDED"] == "Y") ? 'open' : ''?>"><?=$arItem["NAME"] ?></div>
<?
$arCur = current($arItem["VALUES"]);
switch ($arItem["DISPLAY_TYPE"]) :
case "A"://NUMBERS_WITH_SLIDER
case "B"://NUMBERS
case "G"://CHECKBOXES_WITH_PICTURES
case "H"://CHECKBOXES_WITH_PICTURES_AND_LABELS
case "P"://DROPDOWN
case "R"://DROPDOWN_WITH_PICTURES_AND_LABELS
case "K"://RADIO_BUTTONS
case "U"://CALENDAR
echo 'Only checkbox supported';
break;
default: // checkboxes?>
<div class="param-list-wrp">
<? foreach($arItem["VALUES"] as $val => $ar): ?>
<div class="cb-outer">
<input type="checkbox"
name="<? echo $ar["CONTROL_NAME"] ?>"
id="<? echo $ar["CONTROL_ID"] ?>"
class="c-checkbox jsGetItemsCnt"
value="<? echo $ar["HTML_VALUE"] ?>"
<? echo $ar["CHECKED"]? 'checked="checked"': '' ?> >
<label for="<? echo $ar["CONTROL_ID"] ?>" class="c-checkbox__label"><?=$ar["VALUE"];?></label>
<? if ($arParams["DISPLAY_ELEMENT_COUNT"] !== "N" && isset($ar["ELEMENT_COUNT"])):?>
(<span data-role="count_<?=$ar["CONTROL_ID"]?>"><? echo $ar["ELEMENT_COUNT"]; ?></span>)
<?endif;?>
</div>
<?endforeach;?>
<? if (count($arItem["VALUES"]) > 10 ): ?>
<span class="show-all">Показать все</span>
<? endif; ?>
</div> <!-- /.param-list-wrp -->
<?endswitch;?>
</div>
<? endforeach; ?>
<a type="submit" class="button reset-filter jsFilterApply" href="<?=$arResult["SEF_SET_FILTER_URL"]?>">Применить</a>
<a class="button reset-filter jsFilterReset" href="<?=$arResult["SEF_DEL_FILTER_URL"]?>">Сбросить фильтр</a>
</form>
</div> <!-- /.catalog-filter -->
Пример файла partSmartFilter.js
Для создания ползунка используется https://jqueryui.com/slider/
/**
* AJAX запрос на получение данных об отборе фильтром
* @type {string}
*/
function getFilterLinks($this) {
var $form = $this.closest('form');
var $btnApply = $form.find('.jsFilterApply');
var reqData;
reqData = $form.serializeArray();
reqData.push({'name':'ajax', 'value':'y'});
$.ajax({
url: location.protocol + '//' + location.host + location.pathname,
method: 'POST',
type: 'POST',
data: reqData,
dataType:'text',
success: function(obj) {
obj = obj.replace(/\'/g, "\"");
obj = JSON.parse(obj);
// количество отобранных элементов
//obj.ELEMENT_COUNT
// ссылка для применения фильтра
//obj.SEF_SET_FILTER_URL
$btnApply.attr("href", obj.SEF_SET_FILTER_URL);
$form.attr("action", obj.SEF_SET_FILTER_URL);
},
error: function(p1, p2, p3) {
console.log('error');
console.log(p1);
console.log(p2);
console.log(p3);
}
});
}
$(function() {
// Элемент выбора диапазона. Поля ввода значений
var $inputMin = $('input.minCost').first();
var $inputMax = $('input.maxCost').first();
// Элемент выбора диапазона. минимальное значение
var priceMin = parseInt($inputMin.data('start-value'));
// Элемент выбора диапазона. текущее нижнее значение фильтра
var priceMinCurrent = parseInt($inputMin.val());
// Элемент выбора диапазона. максимальное значение
var priceMax = parseInt($inputMax.data('start-value'));
// Элемент выбора диапазона. текущее верхнее значение фильтра
var priceMaxCurrent = parseInt($inputMax.val());
// Элемент выбора диапазона. Сам ползунок
var $rangeSlider = $("#range-slider");
// Сброс настроек предыдущей инициализации
$rangeSlider.slider('destroy');
// Элемент выбора диапазона. Работа ползунка
$rangeSlider.slider({
min: priceMin,
max: priceMax,
values: [priceMinCurrent,priceMaxCurrent],
range: true,
stop: function(event, ui) {
$inputMin.val($rangeSlider.slider("values",0));
$inputMax.val($rangeSlider.slider("values",1));
var $this = $(this);
getFilterLinks($this);
},
slide: function(event, ui){
$inputMin.val($rangeSlider.slider("values",0));
$inputMax.val($rangeSlider.slider("values",1));
}
});
// Синхронизация ползунка при изменении минимального значения в инпуте
$inputMin.change(function(){
var value1=$inputMin.val();
var value2=$inputMax.val();
if(parseInt(value1) > parseInt(value2)){
value1 = value2;
$inputMin.val(value1);
}
$rangeSlider.slider("values",0,value1);
var $this = $(this);
getFilterLinks($this);
console.log('ch min');
});
// Синхронизация ползунка при изменении максимального значения в инпуте
$inputMax.change(function(){
var value1=$inputMin.val();
var value2=$inputMax.val();
if (value2 > priceMax) { value2 = priceMax; $inputMax.val(priceMax)}
if(parseInt(value1) > parseInt(value2)){
value2 = value1;
$inputMax.val(value2);
}
$rangeSlider.slider("values",1,value2);
var $this = $(this);
getFilterLinks($this);
console.log('ch max');
});
// обновление ссылки фильтра при выборе галок
$('.jsGetItemsCnt').on('change', function () {
var $this = $(this);
getFilterLinks($this);
});
});
Подключение компонента фильтра
Подключение компонента фильтра обычно делается в файле section.php комплексного компонента каталога.
<?
if (CModule::IncludeModule("iblock")) {
$arFilter = array (
"ACTIVE" => "Y",
"GLOBAL_ACTIVE" => "Y",
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
);
if(strlen($arResult["VARIABLES"]["SECTION_CODE"])>0) {
$arFilter["=CODE"] = $arResult["VARIABLES"]["SECTION_CODE"];
}
elseif($arResult["VARIABLES"]["SECTION_ID"]>0) {
$arFilter["ID"] = $arResult["VARIABLES"]["SECTION_ID"];
}
$obCache = new CPHPCache;
if($obCache->InitCache(36000, serialize($arFilter), "/iblock/catalog")) {
$arCurSection = $obCache->GetVars();
} else {
$arCurSection = array();
$dbRes = CIBlockSection::GetList(array(), $arFilter, false, array("ID"));
$dbRes = new CIBlockResult($dbRes);
if(defined("BX_COMP_MANAGED_CACHE")) {
global $CACHE_MANAGER;
$CACHE_MANAGER->StartTagCache("/iblock/catalog");
if ($arCurSection = $dbRes->GetNext())
{
$CACHE_MANAGER->RegisterTag("iblock_id_".$arParams["IBLOCK_ID"]);
}
$CACHE_MANAGER->EndTagCache();
} elseif(!$arCurSection = $dbRes->GetNext()) {
$arCurSection = array();
}
$obCache->EndDataCache($arCurSection);
}
if(!$arResult["VARIABLES"]) {
$arCurSection['ID'] = null;
}
}?>
<?
$APPLICATION->IncludeComponent(
"bitrix:catalog.smart.filter",
"",
array(
"IBLOCK_TYPE" => $arParams["IBLOCK_TYPE"],
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
"SECTION_ID" => $arCurSection['ID'],
"FILTER_NAME" => $arParams["FILTER_NAME"],
"PRICE_CODE" => $arParams["PRICE_CODE"],
"CACHE_TYPE" => $arParams["CACHE_TYPE"],
"CACHE_TIME" => $arParams["CACHE_TIME"],
"CACHE_GROUPS" => $arParams["CACHE_GROUPS"],
"SAVE_IN_SESSION" => "N",
"FILTER_VIEW_MODE" => $arParams["FILTER_VIEW_MODE"],
"XML_EXPORT" => "Y",
"SECTION_TITLE" => "NAME",
"SECTION_DESCRIPTION" => "DESCRIPTION",
'HIDE_NOT_AVAILABLE' => $arParams["HIDE_NOT_AVAILABLE"],
"TEMPLATE_THEME" => $arParams["TEMPLATE_THEME"],
'CONVERT_CURRENCY' => $arParams['CONVERT_CURRENCY'],
'CURRENCY_ID' => $arParams['CURRENCY_ID'],
"SEF_MODE" => $arParams["SEF_MODE"],
"SEF_RULE" => $arResult["FOLDER"].$arResult["URL_TEMPLATES"]["smart_filter"],
"SMART_FILTER_PATH" => $arResult["VARIABLES"]["SMART_FILTER_PATH"],
"PAGER_PARAMS_NAME" => $arParams["PAGER_PARAMS_NAME"],
"INSTANT_RELOAD" => $arParams["INSTANT_RELOAD"],
),
$component,
array('HIDE_ICONS' => 'Y')
);
?>