Skip to content

Commit 60ba13c

Browse files
committed
feat(core dom): add_event_listener and remove_event_listener methods to register event handlers for DOM nodes which can be unregistered by an id. Event handlers with the same id on the same node won't be registered twice.
1 parent d29ebad commit 60ba13c

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed

src/core/dom.js

+60
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,64 @@ const create_from_string = (string) => {
102102
return div.firstChild;
103103
};
104104

105+
// Event listener registration for easy-to-remove event listeners.
106+
// once Safari supports the ``signal`` option for addEventListener we can abort
107+
// event handlers by calling AbortController.abort().
108+
const event_listener_map = {};
109+
110+
/**
111+
* Add an event listener to a DOM element under a unique id.
112+
* If a event is registered under the same id for the same element, the old hander is removed first.
113+
*
114+
* @param {DOM Node} el - The element to register the event for.
115+
* @param {string} event_type - The event type to listen for.
116+
* @param {string} id - A unique id under which the event is registered.
117+
* @param {function} cb - The event handler / callback function.
118+
* @param {Object} opts - Options for the addEventListener API.
119+
*
120+
*/
121+
const add_event_listener = (el, event_type, id, cb, opts = {}) => {
122+
if (!el?.addEventListener) {
123+
return; // nothing to do.
124+
}
125+
remove_event_listener(el, id); // do not register one listener twice.
126+
127+
if (!event_listener_map[el]) {
128+
event_listener_map[el] = {};
129+
}
130+
event_listener_map[el][id] = [event_type, cb, opts.capture ? opts : undefined]; // prettier-ignore
131+
el.addEventListener(event_type, cb, opts);
132+
};
133+
134+
/**
135+
* Remove an event listener from a DOM element under a unique id.
136+
*
137+
* @param {DOM Node} el - The element to register the event for.
138+
* @param {string} id - A unique id under which the event is registered.
139+
*
140+
*/
141+
const remove_event_listener = (el, id) => {
142+
if (!el?.removeEventListener) {
143+
return; // nothing to do.
144+
}
145+
const el_events = event_listener_map[el];
146+
if (!el_events) {
147+
return;
148+
}
149+
let entries;
150+
if (id) {
151+
// remove event listener with specific id
152+
const entry = el_events[id];
153+
entries = entry ? [entry] : [];
154+
} else {
155+
// remove all event listeners of element
156+
entries = Object.entries(el_events);
157+
}
158+
for (const entry of entries || []) {
159+
el.removeEventListener(entry[0], entry[1], entry[2]);
160+
}
161+
};
162+
105163
const dom = {
106164
toNodeArray: toNodeArray,
107165
querySelectorAllAndMe: querySelectorAllAndMe,
@@ -113,6 +171,8 @@ const dom = {
113171
get_parents: get_parents,
114172
is_visible: is_visible,
115173
create_from_string: create_from_string,
174+
add_event_listener: add_event_listener,
175+
remove_event_listener: remove_event_listener,
116176
};
117177

118178
export default dom;

src/core/dom.test.js

+47
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,51 @@ describe("core.dom tests", () => {
321321
done();
322322
});
323323
});
324+
325+
describe("add / remove event listener", () => {
326+
const _el = {
327+
event_list: [],
328+
addEventListener(event_type, cb) {
329+
this.event_list.push([event_type, cb]);
330+
},
331+
removeEventListener(event_type, cb) {
332+
const idx = this.event_list.indexOf([event_type, cb]);
333+
this.event_list.splice(idx, 1);
334+
},
335+
};
336+
337+
it("Registers events only once and unregisters events.", (done) => {
338+
const cb1 = () => {};
339+
const cb2 = () => {};
340+
341+
// register one event handler
342+
dom.add_event_listener(_el, "click", "test_click", cb1);
343+
expect(_el.event_list.length).toBe(1);
344+
expect(_el.event_list[0][1]).toBe(cb1);
345+
346+
// register another event hander under the same id
347+
dom.add_event_listener(_el, "click", "test_click", cb2);
348+
expect(_el.event_list.length).toBe(1);
349+
expect(_el.event_list[0][1]).toBe(cb2);
350+
351+
// register two more event handlers with unique ids
352+
dom.add_event_listener(_el, "click", "test_click_2", () => {});
353+
dom.add_event_listener(_el, "click", "test_click_3", () => {});
354+
expect(_el.event_list.length).toBe(3);
355+
356+
// remove one specific event handler
357+
dom.remove_event_listener(_el, "test_click_2");
358+
expect(_el.event_list.length).toBe(2);
359+
360+
// try to remove an unregistered event handler
361+
dom.remove_event_listener(_el, "test_click_4");
362+
expect(_el.event_list.length).toBe(2);
363+
364+
// remove all registered event handlers on that element
365+
dom.remove_event_listener(_el);
366+
expect(_el.event_list.length).toBe(0);
367+
368+
done();
369+
});
370+
});
324371
});

0 commit comments

Comments
 (0)