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

«Делегируй событие – экономь память и нервы»
Быстрый референс
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?
- Работает с динамическим DOM. Элементы, созданные AJAX-ом или Vue/React-виджетами, автоматически «попадают под действие» обработчика.
- Уменьшает утечки памяти – один слушатель вместо сотен.
- Упрощает хранение логики. Всё находится в одном месте, а не раскидано по 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
, сохраняйте ссылку на обработчик и не забывайте о мобильных пользователях.
Попрактикуйтесь с приведёнными примерами, адаптируйте их под свои компоненты, и ваш фронтенд в Битриксе станет надёжнее и быстрее.