Why delegation wins
Binding a handler per row costs memory and setup time, and every row you add later needs binding again. Delegation attaches one listener to a stable ancestor. When an event bubbles up, jsKjs checks event.target.closest(selector) and calls your handler with this set to the matching element.
It also pairs perfectly with Hyper: content swapped in by kj-get is handled automatically, because the listener lives above it.
| # | Order | Total | Actions |
|---|
Click a row to select it.
View the source for this demo
<div class="row">
<button type="button" id="dl-add">Add 250 rows</button>
<button type="button" id="dl-reset">Reset</button>
<span class="chip" id="dl-stats" aria-live="polite">0 rows · 1 listener</span>
</div>
<div class="dl-wrap" tabindex="0" role="region" aria-label="Orders table (scrollable)">
<table>
<thead><tr><th scope="col">#</th><th scope="col">Order</th><th scope="col">Total</th><th scope="col"><span class="sr-only">Actions</span></th></tr></thead>
<tbody id="dl-body"></tbody>
</table>
</div>
<p class="out" id="dl-out" aria-live="polite">Click a row to select it.</p>let n = 0;
const stats = () => $('#dl-stats').text($('#dl-body tr').length + ' rows · 1 listener');
function addRows(count) {
const rows = [];
for (let i = 0; i < count; i++) {
n++;
rows.push('<tr data-id="' + n + '"><td>' + n + '</td><td>Order #' + (1000 + n) + '</td><td>$' + (n * 7 % 90 + 10) +
'</td><td><button type="button" class="dl-del" aria-label="Delete order ' + n + '">✕</button></td></tr>');
}
$('#dl-body').append(rows.join(''));
stats();
}
// The ONLY listener: it serves every row that exists now or later
$('#dl-body').on('click', 'tr', function (e) {
if ($(e.target).closest('.dl-del').length) {
const id = $(this).remove().attr('data-id');
$('#dl-out').text('Deleted row ' + id + ' — nothing to unbind.');
return stats();
}
$(this).toggleClass('sel');
$('#dl-out').text('Row ' + this.dataset.id + ' toggled · ' + $('#dl-body tr.sel').length + ' selected');
});
$('#dl-add').on('click', () => addRows(250));
$('#dl-reset').on('click', () => { $('#dl-body').empty(); n = 0; addRows(20); $('#dl-out').text('Reset.'); });
addRows(20);More examples
Delegate from the document for app-wide behaviour
$(document).on('click', '[data-copy]', function () {
navigator.clipboard.writeText(this.dataset.copy);
});
$(document).on('submit', 'form[data-confirm]', function (e) {
if (!confirm(this.dataset.confirm)) e.preventDefault();
});API
Attach handlers. Multiple space-separated events and .namespace suffixes are supported. With a delegate selector, the handler fires for matching descendants — including ones added later — and this is the matched element.
Nearest matching ancestor (including the element itself). The backbone of event delegation.
Reference: .closest() →Migrating
.on(events, selector, handler). The old .live() and .delegate() forms are gone in favour of this one.Related features
Events
Bind and unbind events with on(), off() and one(), group handlers with namespaces like click.menu, and use shortcut methods — the jQuery event API on native addEventListener..
Open →CoreDOM traversal
Walk the DOM with find(), closest(), parent(), parents(), children(), siblings(), next(), prev(), filter(), not() and is() — every result chainable and de-duplicated..
Open →CoreDOM manipulation
Insert, move, clone, wrap and remove elements with the jQuery API you know — .append(), .prepend(), .before(), .after(), .clone(), .empty() — on modern DOM primitives..
Open →