Bitrix - quick tree multilevel menu accordion catalog
The last notes
All English-language materials have been translated fully automatically using the Google service
Once again, the task arose of building a tree-like accordion menu based on the catalog sections. When solving the problem, I wanted to minimize the number of requests, make it as fast and simple as possible, which we managed to do
Template
use Class\Path\Script;
global $sections;
$sections = Script::getSections();
foreach ($sections as $k => $v){
$sections[$k]['DETAIL_PAGE_URL'] = Script::makeUrl($v['ID']);
}
$sections = Script::buildTree($sections);
if($sections){
?><div class="accordion-menu col-menu">
<?=Script::buildMenu($sections)?>
</div><?
}
Class
<?php namespace Class\Path\Script;
/**
* Класс помощник при работу с датами
*/
class Script{
/**
* Получаем ссылку на раздел по его id
* @param $parentId integer
* @return string
*/
public static function makeUrl($parentId = 0){
global $sections;
$sefFolder = '/catalog/';
$codes = array();
while (isset($parentId)) {
$codes[] = $sections[$parentId]['CODE'];
if(isset($sections[$parentId])){
$parentId = $sections[$parentId]['IBLOCK_SECTION_ID'];
}else{
$parentId = null;
}
}
if (sizeof($codes)) {
return $sefFolder . implode('/', array_reverse($codes)) . '/';
} else {
return null;
}
}
/**
* Получаем перечень разделов
* @param $data array
* @return array
*/
public static function getSections(){
$sections = [];
\CModule::IncludeModule("iblock");
$resCatalog = \CIBlockSection::GetList(
array(
'LEFT_MARGIN' => 'ASC'
),
array(
'IBLOCK_ID' => Class\Path\Config::getCatalogIB(),
'ACTIVE' => 'Y',
'GLOBAL_ACTIVE' => 'Y',
),
false,
array(
'IBLOCK_ID',
'IBLOCK_SECTION_ID',
'NAME',
'DEPTH_LEVEL',
'SECTION_PAGE_URL',
'UF_*'
)
);
while($arCatalog = $resCatalog->Fetch()){
$sections[$arCatalog['ID']] = $arCatalog;
}
return $sections;
}
/**
* Строим древовидную структуру разделов
* @param $arResult array
* @return array
*/
public static function buildTree($arResult){
if (!empty($arResult)) {
$parentID = false;
$subParentID = false;
$subsubParentID = false;
foreach($arResult as $i => $arItem) {
if ($arItem['DEPTH_LEVEL'] == 1) {
$parentID = $i;
$arResult[$i]['ITEMS'] = array();
} elseif ($arItem['DEPTH_LEVEL']==2 && $parentID!==false) {
$arResult[$parentID]['ITEMS'][$i] = $arItem;
$subParentID = $i;
unset($arResult[$i]);
} elseif ($arItem['DEPTH_LEVEL']==3 && isset($arResult[$parentID]['ITEMS'][$subParentID])) {
$arResult[$parentID]['ITEMS'][$subParentID]['ITEMS'][$i] = $arItem;
$subsubParentID = $i;
unset($arResult[$i]);
} elseif ($arItem['DEPTH_LEVEL']==4) {
$arResult[$parentID]['ITEMS'][$subParentID]['ITEMS'][$subsubParentID]['ITEMS'][$i] = $arItem;
unset($arResult[$i]);
}
}
$arResult = array_values($arResult);
}
return $arResult;
}
/**
* Строим древовидную структуру разделов
* @param $arResult array
* @return array
*/
public static function buildMenu($array)
{
echo '<ul>';
global $APPLICATION;
foreach ($array as $item)
{
echo '<li>';
echo '<a href="'.$item['DETAIL_PAGE_URL'].'" '.($item['DETAIL_PAGE_URL'] == $APPLICATION->GetCurPage() ? 'class="selected"' : '').'>'.$item['NAME'].'</a>';
if (!empty($item['ITEMS']))
{
Script::buildMenu($item['ITEMS'],false);
}
echo '</li>';
}
echo '</ul>';
}
}
Javascript
let AccordionMenu = function(selector) {
this.colMenu = document.querySelectorAll(`${selector} li`);
let This = this;
this.colMenu.forEach(function(items) {
if (items.querySelector('ul')) {
items.firstElementChild.insertAdjacentHTML('beforeend', '<div class="menu--svg"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 451.847 451.847" xml:space="preserve"> <g> <path d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751 c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0 c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"/> </g> </svg></div>');
items.querySelector('.menu--svg').onclick = function(e) {
if(e.target.closest('.menu--svg')){
e.preventDefault();
let isTrue = this.closest('li').classList.toggle('open');
if (isTrue) {
This.show(e.target.closest('a').nextElementSibling);
} else {
This.hide(e.target.closest('a').nextElementSibling);
}
}
}
}
})
}
// Show an element
AccordionMenu.prototype.show = function(elem) {
// Get the natural height of the element
var getHeight = function() {
elem.style.display = 'block'; // Make it visible
var height = elem.scrollHeight + 'px'; // Get it's height
return height;
};
var height = getHeight(); // Get the natural height
elem.style.height = height; // Update the height
setTimeout(function() {
elem.style.height = 'auto';
}, 350);
};
// Hide an element
AccordionMenu.prototype.hide = function(elem) {
// Give the element a height to change from
elem.style.height = elem.scrollHeight + 'px';
// Set the height back to 0
setTimeout(function() {
elem.style.height = '0';
}, 110);
setTimeout(function() {
elem.style.display = '';
}, 700);
};
new AccordionMenu('.col-menu');
/* Простите за этот участок jQuery - мне хотелось быстрого и лаконичного решения */
(function($) {
$( document ).ready(function() {
$('.col-menu a.selected').parents('li').addClass('open');
$('.col-menu a.selected').parents('ul').css({'display' : 'block', 'height':'auto'});
$('.col-menu a.selected').next().css({'display' : 'block', 'height':'auto'});
});
})(jQuery);
css
/* Multilevel Accordion Menu with Plain HTML & CSS Start */
.accordion-menu {
padding: 4px 4px 3px;
background: rgba(255,255,255,0.5);
}
.accordion-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.accordion-menu li.open > ul {
display: block;
}
.accordion-menu li > ul {
/* position: absolute; */
display: none;
height: 0;
overflow: hidden;
transition: height 350ms ease-in-out;
}
.accordion-menu ul.is-visible {
display: block;
/* height: auto; */
}
.accordion-menu li {
border-bottom: 1px solid rgba(194, 194, 194, 0.333);
position: relative;
overflow: hidden;
transition: all .4s ease;
}
.accordion-menu li:last-child {
border: none;
}
.accordion-menu li::after {
content: "";
display: block;
clear: both;
}
.accordion-menu li > a > .menu--svg > svg {
display: block;
position: relative;
fill: rgb(48, 48, 48);
width: 10px;
float: right;
transition: all .3s ease;
}
.accordion-menu li.open > a > .menu--svg > svg {
transform: rotate(90deg);
}
.accordion-menu a {
background: rgba(255,255,255,0.7);
color: #444;
padding: 7px 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
text-decoration: none;
text-transform: uppercase;
margin: 0 0 1px 0;
}
.accordion-menu li.open > a.selected {
/*background: rgba(255,255,255,1);*/
background: rgba(0,0,0,0.1);
color: #bc0003;
font-weight: 600;
}
.accordion-menu li.open > a {font-weight: 600;background: rgba(0,0,0,0.1);}
.accordion-menu a:hover{background: rgba(50,50,50,1);color: #fff;}
.accordion-menu a:hover svg{fill: #fff !important;}
.accordion-menu li li a{padding-left: 30px;}
.accordion-menu li li li a{padding-left: 40px;}
.accordion-menu li li li li a{padding-left: 50px;}
.accordion-menu * {box-sizing: border-box;}
.menu--svg {width:20px;height: 20px;display: flex;align-items: center;justify-content: center;flex-direction: column;}
.menu--svg:hover {background-color: #000}
/* Multilevel Accordion Menu with Plain HTML & CSS Ends */
The result looks like this:
Based on materials:
Comments