Development of a multi-level menu module for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Developing a Multi-Level Menu Module for 1C-Bitrix

The standard bitrix:menu component generates menus from .menu.php files or from the site structure. For simple sites this is sufficient. Problems begin when a marketer wants to manage a mega-menu with pictures, banners and columns through an administrative interface without code changes, when different menu structures need to be A/B tested, or when different menu items need to be shown to authorized and anonymous users. A multi-level menu module provides flexible management through an interface with caching and full mega-menu support.

Data Model

Module vendor.megamenu:

  • b_vendor_menu_config — menu configurations: id, code, name, site_id, lang, is_active
  • b_vendor_menu_item — menu items: id, menu_id, parent_id, sort, title, url, url_type (absolute/relative/component), target, icon_id, image_id, css_class, visibility (all/authorized/unauthorized), condition (JSON), is_active
  • b_vendor_menu_column — mega-menu columns for second-level items: id, item_id, title, sort, items (JSON — links without separate records)
  • b_vendor_menu_banner — banners in mega-menu: id, item_id, image_id, url, title, sort

Tree of Menu Items

Items are stored in adjacency list (parent_id). To build the tree, iterative traversal is used to avoid recursion in PHP:

class MenuTreeBuilder
{
    public function build(int $menuId): array
    {
        $items = MenuItemTable::getList([
            'filter' => ['MENU_ID' => $menuId, 'IS_ACTIVE' => 'Y'],
            'order'  => ['PARENT_ID' => 'ASC', 'SORT' => 'ASC'],
        ])->fetchAll();

        $tree  = [];
        $index = [];

        foreach ($items as $item) {
            $item['children'] = [];
            $index[$item['ID']] = &$item;
        }

        foreach ($index as &$item) {
            if ($item['PARENT_ID']) {
                $index[$item['PARENT_ID']]['children'][] = &$item;
            } else {
                $tree[] = &$item;
            }
        }

        return $tree;
    }
}

The tree is cached entirely with the tag menu_{code}_{lang}. Invalidation occurs when any item or banner of this menu changes.

Item Visibility

Menu item visibility conditions:

  • visibility = 'authorized' — item is hidden from guests
  • visibility = 'unauthorized' — item is hidden from logged-in users
  • condition (JSON) — arbitrary conditions: user groups, region, request parameters
class VisibilityChecker
{
    public function isVisible(array $item): bool
    {
        global $USER;

        if ($item['VISIBILITY'] === 'authorized' && !$USER->IsAuthorized()) return false;
        if ($item['VISIBILITY'] === 'unauthorized' && $USER->IsAuthorized()) return false;

        $condition = $item['CONDITION'] ?? [];
        if (!empty($condition['user_groups'])) {
            $userGroups = $USER->GetUserGroupArray();
            if (!array_intersect($condition['user_groups'], $userGroups)) return false;
        }

        return true;
    }
}

Visibility conditions are applied after retrieving the cached tree — the cache stores the complete tree, filtering occurs in memory.

Mega-Menu with Columns and Banners

For top-level items, a mega-menu dropdown with columns can be configured:

[Catalog]
├─ Column 1: Electronics
│   ├─ Smartphones
│   ├─ Laptops
│   └─ Tablets
├─ Column 2: Home Appliances
│   ├─ Refrigerators
│   └─ Washing Machines
└─ Banner: [Weekly Sales → /sale/]

Columns are stored in b_vendor_menu_column, banners in b_vendor_menu_banner. The component generates the HTML structure for the mega-menu, styling is done through CSS Grid.

Drag-and-drop Interface

The administrative interface is built on Sortable.js (or similar). Items are dragged to the desired position and parent element. When dragging, an AJAX request updates sort and parent_id in a single transaction.

Active Menu Item

The active menu item is determined by the current URL:

// Exact URL match or path prefix match
function isActive(array $item, string $currentUrl): bool
{
    if ($item['URL'] === $currentUrl) return true;
    if ($item['URL_TYPE'] === 'section' && str_starts_with($currentUrl, $item['URL'])) return true;
    return false;
}

The active item and all its ancestors receive the CSS class active.

Development Timeline

Stage Duration
ORM-tables, tree model 1 day
Building tree, caching 1 day
Item visibility conditions 1 day
Mega-menu: columns, banners 2 days
Drag-and-drop interface 2 days
Site components (HTML + CSS) 2 days
Testing 1 day

Total: 10 working days. For mobile menu (offcanvas, hamburger) — additional 1 day.