Skip to content

Commit

Permalink
feat(core dom): add_event_listener and remove_event_listener methods …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
thet committed Sep 8, 2021
1 parent d29ebad commit 60ba13c
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 0 deletions.
60 changes: 60 additions & 0 deletions src/core/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,64 @@ const create_from_string = (string) => {
return div.firstChild;
};

// Event listener registration for easy-to-remove event listeners.
// once Safari supports the ``signal`` option for addEventListener we can abort
// event handlers by calling AbortController.abort().
const event_listener_map = {};

/**
* Add an event listener to a DOM element under a unique id.
* If a event is registered under the same id for the same element, the old hander is removed first.
*
* @param {DOM Node} el - The element to register the event for.
* @param {string} event_type - The event type to listen for.
* @param {string} id - A unique id under which the event is registered.
* @param {function} cb - The event handler / callback function.
* @param {Object} opts - Options for the addEventListener API.
*
*/
const add_event_listener = (el, event_type, id, cb, opts = {}) => {
if (!el?.addEventListener) {
return; // nothing to do.
}
remove_event_listener(el, id); // do not register one listener twice.

if (!event_listener_map[el]) {
event_listener_map[el] = {};
}
event_listener_map[el][id] = [event_type, cb, opts.capture ? opts : undefined]; // prettier-ignore
el.addEventListener(event_type, cb, opts);
};

/**
* Remove an event listener from a DOM element under a unique id.
*
* @param {DOM Node} el - The element to register the event for.
* @param {string} id - A unique id under which the event is registered.
*
*/
const remove_event_listener = (el, id) => {
if (!el?.removeEventListener) {
return; // nothing to do.
}
const el_events = event_listener_map[el];
if (!el_events) {
return;
}
let entries;
if (id) {
// remove event listener with specific id
const entry = el_events[id];
entries = entry ? [entry] : [];
} else {
// remove all event listeners of element
entries = Object.entries(el_events);
}
for (const entry of entries || []) {
el.removeEventListener(entry[0], entry[1], entry[2]);
}
};

const dom = {
toNodeArray: toNodeArray,
querySelectorAllAndMe: querySelectorAllAndMe,
Expand All @@ -113,6 +171,8 @@ const dom = {
get_parents: get_parents,
is_visible: is_visible,
create_from_string: create_from_string,
add_event_listener: add_event_listener,
remove_event_listener: remove_event_listener,
};

export default dom;
47 changes: 47 additions & 0 deletions src/core/dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,51 @@ describe("core.dom tests", () => {
done();
});
});

describe("add / remove event listener", () => {
const _el = {
event_list: [],
addEventListener(event_type, cb) {
this.event_list.push([event_type, cb]);
},
removeEventListener(event_type, cb) {
const idx = this.event_list.indexOf([event_type, cb]);
this.event_list.splice(idx, 1);
},
};

it("Registers events only once and unregisters events.", (done) => {
const cb1 = () => {};
const cb2 = () => {};

// register one event handler
dom.add_event_listener(_el, "click", "test_click", cb1);
expect(_el.event_list.length).toBe(1);
expect(_el.event_list[0][1]).toBe(cb1);

// register another event hander under the same id
dom.add_event_listener(_el, "click", "test_click", cb2);
expect(_el.event_list.length).toBe(1);
expect(_el.event_list[0][1]).toBe(cb2);

// register two more event handlers with unique ids
dom.add_event_listener(_el, "click", "test_click_2", () => {});
dom.add_event_listener(_el, "click", "test_click_3", () => {});
expect(_el.event_list.length).toBe(3);

// remove one specific event handler
dom.remove_event_listener(_el, "test_click_2");
expect(_el.event_list.length).toBe(2);

// try to remove an unregistered event handler
dom.remove_event_listener(_el, "test_click_4");
expect(_el.event_list.length).toBe(2);

// remove all registered event handlers on that element
dom.remove_event_listener(_el);
expect(_el.event_list.length).toBe(0);

done();
});
});
});

0 comments on commit 60ba13c

Please sign in to comment.