Use event delegation (#644)

Instead of attaching a new root-level event listener for bubbling events
for every choices instance, use a simple event delegation script to
handle each event type.

Each event callback function already is coded as if it were fully
delegated, since the events are attached at the document level, so
no changes are needed to detect which element is being called.

Note that focus and blur event do not bubble, so they have been left as
they are.

Also note that the event delegation uses an IIFE purposely instead of
ES6 modules, since the event list should be globally cached, and it
doesn't make sense to instantiate a new scope for each instance (then
we're back where we started!)

fix #643
This commit is contained in:
Chris DeLuca 2019-10-15 03:42:31 -04:00 committed by Josh Johnson
parent 5cf226f166
commit e7d775e2ae
2 changed files with 54 additions and 14 deletions

View file

@ -1,6 +1,7 @@
import Fuse from 'fuse.js';
import merge from 'deepmerge';
import './lib/delegate-events';
import Store from './store/store';
import {
Dropdown,
@ -1072,13 +1073,13 @@ class Choices {
}
_addEventListeners() {
document.addEventListener('keyup', this._onKeyUp);
document.addEventListener('keydown', this._onKeyDown);
document.addEventListener('click', this._onClick);
document.addEventListener('touchmove', this._onTouchMove);
document.addEventListener('touchend', this._onTouchEnd);
document.addEventListener('mousedown', this._onMouseDown);
document.addEventListener('mouseover', this._onMouseOver);
window.delegateEvent.add('keyup', this._onKeyUp);
window.delegateEvent.add('keydown', this._onKeyDown);
window.delegateEvent.add('click', this._onClick);
window.delegateEvent.add('touchmove', this._onTouchMove);
window.delegateEvent.add('touchend', this._onTouchEnd);
window.delegateEvent.add('mousedown', this._onMouseDown);
window.delegateEvent.add('mouseover', this._onMouseOver);
if (this._isSelectOneElement) {
this.containerOuter.element.addEventListener('focus', this._onFocus);
@ -1096,13 +1097,13 @@ class Choices {
}
_removeEventListeners() {
document.removeEventListener('keyup', this._onKeyUp);
document.removeEventListener('keydown', this._onKeyDown);
document.removeEventListener('click', this._onClick);
document.removeEventListener('touchmove', this._onTouchMove);
document.removeEventListener('touchend', this._onTouchEnd);
document.removeEventListener('mousedown', this._onMouseDown);
document.removeEventListener('mouseover', this._onMouseOver);
window.delegateEvent.remove('keyup', this._onKeyUp);
window.delegateEvent.remove('keydown', this._onKeyDown);
window.delegateEvent.remove('click', this._onClick);
window.delegateEvent.remove('touchmove', this._onTouchMove);
window.delegateEvent.remove('touchend', this._onTouchEnd);
window.delegateEvent.remove('mousedown', this._onMouseDown);
window.delegateEvent.remove('mouseover', this._onMouseOver);
if (this._isSelectOneElement) {
this.containerOuter.element.removeEventListener('focus', this._onFocus);

View file

@ -0,0 +1,39 @@
window.delegateEvent = (function delegateEvent() {
let events;
let addedListenerTypes;
if (typeof events === 'undefined') {
events = new Map();
}
if (typeof addedListenerTypes === 'undefined') {
addedListenerTypes = [];
}
function _callback(event) {
const type = events.get(event.type);
if (!type) return;
type.forEach(fn => fn(event));
}
return {
add: function add(type, fn) {
// Cache list of events.
if (events.has(type)) {
events.get(type).push(fn);
} else {
events.set(type, [fn]);
}
// Setup events.
if (addedListenerTypes.indexOf(type) === -1) {
document.documentElement.addEventListener(type, _callback, true);
addedListenerTypes.push(type);
}
},
remove: function remove(type, fn) {
if (!events.get(type)) return;
events.set(type, events.get(type).filter(item => item !== fn));
if (!events.get(type).length) {
addedListenerTypes.splice(addedListenerTypes.indexOf(type), 1);
}
},
};
})();