Блог разработчика 1С-Битрикс

Обработка событий в 1С-Битрикс: обзор BX.bindDelegate

Метод BX.bindDelegate ― один из самых эффективных способов «поймать» событие на динамически изменяющихся страницах Битрикс. Он устраняет проблему, когда элементы создаются JavaScript-ом уже после подключения обработчиков, и вам приходится заново «привязывать» события. Ниже разберём синтаксис, подводные камни и дадим как можно больше практических примеров.

Обработка событий. BX.bindDelegate

«Делегируй событие – экономь память и нервы»

Быстрый референс


BX.bindDelegate(
    /** @type {HTMLElement} */ parentNode,  // Родитель-контейнер
    /** @type {String} */ eventName,   // Имя DOM-события
    /** @type {Object} */ isTarget,    // Фильтр целевых узлов
    /** @type {Function} */ handler      // Колбэк-обработчик
);
Параметр Тип Что означает
parentNode HTMLElement Узел, на который вешается единственный реальный обработчик
eventName String Любое поддерживаемое браузером событие («click», «input», «keydown» и т.д.)
isTarget Object Фильтр: указываем свойства, которые должен иметь дочерний узел-источник
handler Function Выполняется при всплытии события, если узел прошёл фильтр

Возвращаемое значение – ссылка на фактически назначенную функцию-обработчик. Её можно позже снять через BX.unbind.

Почему именно delegation?

  1. Работает с динамическим DOM. Элементы, созданные AJAX-ом или Vue/React-виджетами, автоматически «попадают под действие» обработчика.
  2. Уменьшает утечки памяти – один слушатель вместо сотен.
  3. Упрощает хранение логики. Всё находится в одном месте, а не раскидано по init-скриптам.

Настраиваем фильтр isTarget


{
    tagName  : 'A',          // По тегу
    className: 'btn--blue',  // По классу
    attr     : {             // По наличию атрибутов
        'data-role': 'save'
    }
}

Фильтр может содержать любое подмножество полей:

  • tagName — сравнивается без учёта регистра
  • className / class — должен содержать указанный класс
  • attr — объект вида { 'data-id': '15' }, значения сравниваются как строки
  • id — строгое совпадение

Можно передать и функцию:


isTarget: function(node) {
    return node.dataset && node.dataset.track === 'subscribe';
}

Практика: пять живых примеров

1. Игровое «Крестики-нолики» в таблице


<table id="ticTac" class="tictac">
    <tr>
        <td></td>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
        <td></td>
    </tr>
</table>

<style>
.tictac {
    border-collapse: collapse;
}
.tictac td {
    width: 34px;
    height: 34px;
    text-align: center;
    font: 20px/34px monospace;
    border: 1px solid #aaa;
    cursor: pointer;
}
</style>

<script>
(() => {
    let current = 'X';
    const handlerRef = BX.bindDelegate(
        BX('ticTac'),
        'click',
        { tagName: 'TD' },
        function() {
            if (this.textContent === '') {
                this.textContent = current;
                current = current === 'X' ? 'O' : 'X';
            }
        }
    );
    // Хотите отменить игру?
    // BX.unbind(BX('ticTac'), 'click', handlerRef);
})();
</script>

2. Подсветка активного пункта меню без перезагрузки


<ul id="nav" class="nav">
    <li><a href="#">Главная</a></li>
    <li><a href="#">Услуги</a></li>
    <li><a href="#">Контакты</a></li>
</ul>

<style>
.nav a {
    padding: 6px 10px;
    display: inline-block;
}
.nav .on {
    background:#0066cc;
    color:#fff;
    border-radius:4px;
}
</style>

<script>
BX.bindDelegate(
    BX('nav'),
    'click',
    { tagName: 'A' },
    function(event) {
        event.preventDefault();
        // Снимаем старый класс
        BX.findChildren(BX('nav'), { className: 'on' }, true)
          .forEach(node => BX.removeClass(node, 'on'));
        // Ставим новый
        BX.addClass(this, 'on');
    }
);
</script>

Хоть пункты меню могли быть вставлены через компонент bitrix:menu, мы застрахованы: делегирование «увидит» и поздние вставки.

3. Бесконечный список (infinite scroll) и делегирование на «Подробнее»


<div id="feed"></div>

<script>
// Эмулируем AJAX-дозагрузку
function loadChunk() {
    for (let i = 0; i < 3; i++) {
        BX.append(BX.create('DIV', {
            attrs: { className: 'item', 'data-id': Date.now() + i },
            html: `Карточка #${i+1}
                  <button class="btnMore">Подробнее</button>`
        }), BX('feed'));
    }
}
loadChunk(); // первая порция
BX.bindDelegate(
    BX('feed'),
    'click',
    { className: 'btnMore' },
    function() {
        alert('Карточка id = ' + this.parentNode.dataset.id);
    }
);
// Допустим, вы подгружаете ещё данные:
document.addEventListener('scroll', () => {
    if (window.innerHeight + scrollY >= document.body.offsetHeight - 200) {
        loadChunk();
    }
});
</script>

Без делегирования пришлось бы после каждого loadChunk() проходиться по кнопкам и вешать им onclick.

4. Валидация формовых инпутов «на лету»


<form id="regForm">
    <input type="text" name="email" placeholder="E-mail">
    <input type="text" name="phone" placeholder="Телефон">
    <input type="submit" value="Отправить">
</form>

<style>
.input-error {
    border: 1px solid red;
}
</style>

<script>
const emailRx = /^[\w.-]+@[\w.-]+\.[a-z]{2,}$/i;
BX.bindDelegate(
    BX('regForm'),
    'input',
    { tagName: 'INPUT' },
    function() {
        if (this.name === 'email') {
            BX.toggleClass(this, 'input-error', !emailRx.test(this.value));
        }
    }
);
</script>

Событие input всплывает, поэтому слушатель можно разместить прямо на теге <form>.

5. Одновременная привязка на несколько событий


['mouseover', 'focus'].forEach(evt => {
    BX.bindDelegate(
        BX('nav'),
        evt,
        { tagName: 'A' },
        function() { BX.addClass(this, 'hint'); }
    );
});

6. Фильтр-функция для кастомных условий


BX.bindDelegate(
    document.body,
    'click',
    function(node) {
        // Клик только по элементам data-role="like"
        return node.dataset && node.dataset.role === 'like';
    },
    function() {
        BX.toggleClass(this, 'active');
    }
);

Типичные ошибки и как их избежать

Ошибка Причина Как исправить
Делегирование не работает на мобильных Используете click, а на мобильных иногда срабатывает touchstart Слушайте несколько событий (click, touchstart) или пользуйтесь pointerdown
Селектор в isTarget слишком общий Срабатывает на лишние элементы Уточните className, attr или используйте функцию-фильтр
Потеряли ссылку на обработчик, не можете его снять Не сохранили ссылку, которую возвращает BX.bindDelegate Запишите результат в переменную или атрибут объекта

Снятие обработчиков


const ref = BX.bindDelegate(...);
// Позже
BX.unbind(parentNode, 'click', ref);

Указав ту же функцию (а не анонимную), мы снимаем ровно один обработчик.

Итоги

  • BX.bindDelegate — для работы с динамическим интерфейсом в 1С-Битрикс.
  • Делегирование экономит память, убирает дублирование кода и упрощает поддержку SPA-подобных разделов.
  • Используйте правильные фильтры в isTarget, сохраняйте ссылку на обработчик и не забывайте о мобильных пользователях.

Попрактикуйтесь с приведёнными примерами, адаптируйте их под свои компоненты, и ваш фронтенд в Битриксе станет надёжнее и быстрее.

Теги:  BX.bindDelegate, обработка событий, JavaScript, делегирование событий


Стоимость услуг по разработке и сопровождению сайтов на 1C-Битрикс

Интернет-магазин на готовом решении

от 7 дней

от 40 000 рублей
запуск сайта в максимально короткие сроки

* указана минимальная стоимость. Стоимость выбранной лицензии «1С-Битрикс» оплачивается отдельно.

Лендинг

от 3 дней

от 25 000 рублей

Разработка одностраничного сайта на платформе Битрикс

* стоимость зависит от наличия верстки, использования готового решения и т.д.

Разработка корпоративного сайта

от 7 дней

от 40 000 рублей

Разработка сайта без системы оплаты заказов через корзину

* стоимость зависит от наличия верстки, использования готового решения и т.д.