From e7d775e2ae02bbe768cdc1969a2b44f965da9373 Mon Sep 17 00:00:00 2001 From: Chris DeLuca Date: Tue, 15 Oct 2019 03:42:31 -0400 Subject: [PATCH] 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 --- src/scripts/choices.js | 29 +++++++++++----------- src/scripts/lib/delegate-events.js | 39 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 src/scripts/lib/delegate-events.js diff --git a/src/scripts/choices.js b/src/scripts/choices.js index 8a059cb..a5ad90a 100644 --- a/src/scripts/choices.js +++ b/src/scripts/choices.js @@ -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); diff --git a/src/scripts/lib/delegate-events.js b/src/scripts/lib/delegate-events.js new file mode 100644 index 0000000..e327b73 --- /dev/null +++ b/src/scripts/lib/delegate-events.js @@ -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); + } + }, + }; +})();