Simplify events and store listeners only in one place

This commit is contained in:
ahocevar
2019-09-04 16:39:32 +02:00
parent d416866108
commit ebfb20440a
52 changed files with 224 additions and 599 deletions
+15 -191
View File
@@ -7,10 +7,6 @@ import {clear} from './obj.js';
/**
* Key to use with {@link module:ol/Observable~Observable#unByKey}.
* @typedef {Object} EventsKey
* @property {Object} [bindTo]
* @property {ListenerFunction} [boundListener]
* @property {boolean} callOnce
* @property {number} [deleteIndex]
* @property {ListenerFunction} listener
* @property {import("./events/Target.js").EventTargetLike} target
* @property {string} type
@@ -27,119 +23,6 @@ import {clear} from './obj.js';
*/
/**
* @param {EventsKey} listenerObj Listener object.
* @return {ListenerFunction} Bound listener.
*/
export function bindListener(listenerObj) {
let boundListener;
if (listenerObj.bindTo || listenerObj.callOnce) {
boundListener = function(evt) {
const listener = listenerObj.listener;
const bindTo = listenerObj.bindTo || listenerObj.target;
if (listenerObj.callOnce) {
unlistenByKey(listenerObj);
}
return listener.call(bindTo, evt);
};
} else {
boundListener = listenerObj.listener;
}
listenerObj.boundListener = boundListener;
return boundListener;
}
/**
* Finds the matching {@link module:ol/events~EventsKey} in the given listener
* array.
*
* @param {!Array<!EventsKey>} listeners Array of listeners.
* @param {!Function} listener The listener function.
* @param {Object=} opt_this The `this` value inside the listener.
* @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching
* listener, for {@link module:ol/events~unlistenByKey}.
* @return {EventsKey|undefined} The matching listener object.
*/
export function findListener(listeners, listener, opt_this, opt_setDeleteIndex) {
let listenerObj;
for (let i = 0, ii = listeners.length; i < ii; ++i) {
listenerObj = listeners[i];
if (listenerObj.listener === listener &&
listenerObj.bindTo === opt_this) {
if (opt_setDeleteIndex) {
listenerObj.deleteIndex = i;
}
return listenerObj;
}
}
return undefined;
}
/**
* @param {import("./events/Target.js").EventTargetLike} target Target.
* @param {string} type Type.
* @return {Array<EventsKey>|undefined} Listeners.
*/
export function getListeners(target, type) {
const listenerMap = getListenerMap(target);
return listenerMap ? listenerMap[type] : undefined;
}
/**
* Get the lookup of listeners.
* @param {Object} target Target.
* @param {boolean=} opt_create If a map should be created if it doesn't exist.
* @return {!Object<string, Array<EventsKey>>} Map of
* listeners by event type.
*/
function getListenerMap(target, opt_create) {
let listenerMap = target.ol_lm;
if (!listenerMap && opt_create) {
listenerMap = target.ol_lm = {};
}
return listenerMap;
}
/**
* Remove the listener map from a target.
* @param {Object} target Target.
*/
function removeListenerMap(target) {
delete target.ol_lm;
}
/**
* Clean up all listener objects of the given type. All properties on the
* listener objects will be removed, and if no listeners remain in the listener
* map, it will be removed from the target.
* @param {import("./events/Target.js").EventTargetLike} target Target.
* @param {string} type Type.
*/
function removeListeners(target, type) {
const listeners = getListeners(target, type);
if (listeners) {
for (let i = 0, ii = listeners.length; i < ii; ++i) {
/** @type {import("./events/Target.js").default} */ (target).
removeEventListener(type, listeners[i].boundListener);
clear(listeners[i]);
}
listeners.length = 0;
const listenerMap = getListenerMap(target);
if (listenerMap) {
delete listenerMap[type];
if (Object.keys(listenerMap).length === 0) {
removeListenerMap(target);
}
}
}
}
/**
* Registers an event listener on an event target. Inspired by
* https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html
@@ -156,31 +39,23 @@ function removeListeners(target, type) {
* @return {EventsKey} Unique key for the listener.
*/
export function listen(target, type, listener, opt_this, opt_once) {
const listenerMap = getListenerMap(target, true);
let listeners = listenerMap[type];
if (!listeners) {
listeners = listenerMap[type] = [];
if (opt_this && opt_this !== target) {
listener = listener.bind(opt_this);
}
let listenerObj = findListener(listeners, listener, opt_this, false);
if (listenerObj) {
if (!opt_once) {
// Turn one-off listener into a permanent one.
listenerObj.callOnce = false;
}
} else {
listenerObj = {
bindTo: opt_this,
callOnce: !!opt_once,
listener: listener,
target: target,
type: type
if (opt_once) {
const originalListener = listener;
listener = function() {
target.removeEventListener(type, listener);
originalListener.apply(this, arguments);
};
/** @type {import("./events/Target.js").default} */ (target).
addEventListener(type, bindListener(listenerObj));
listeners.push(listenerObj);
}
return listenerObj;
const eventsKey = {
target: target,
type: type,
listener: listener
};
target.addEventListener(type, listener);
return eventsKey;
}
@@ -209,30 +84,6 @@ export function listenOnce(target, type, listener, opt_this) {
}
/**
* Unregisters an event listener on an event target. Inspired by
* https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html
*
* To return a listener, this function needs to be called with the exact same
* arguments that were used for a previous {@link module:ol/events~listen} call.
*
* @param {import("./events/Target.js").EventTargetLike} target Event target.
* @param {string} type Event type.
* @param {ListenerFunction} listener Listener.
* @param {Object=} opt_this Object referenced by the `this` keyword in the
* listener. Default is the `target`.
*/
export function unlisten(target, type, listener, opt_this) {
const listeners = getListeners(target, type);
if (listeners) {
const listenerObj = findListener(listeners, listener, opt_this, true);
if (listenerObj) {
unlistenByKey(listenerObj);
}
}
}
/**
* Unregisters event listeners on an event target. Inspired by
* https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html
@@ -244,34 +95,7 @@ export function unlisten(target, type, listener, opt_this) {
*/
export function unlistenByKey(key) {
if (key && key.target) {
/** @type {import("./events/Target.js").default} */ (key.target).
removeEventListener(key.type, key.boundListener);
const listeners = getListeners(key.target, key.type);
if (listeners) {
const i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key);
if (i !== -1) {
listeners.splice(i, 1);
}
if (listeners.length === 0) {
removeListeners(key.target, key.type);
}
}
key.target.removeEventListener(key.type, key.listener);
clear(key);
}
}
/**
* Unregisters all event listeners on an event target. Inspired by
* https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html
*
* @param {import("./events/Target.js").EventTargetLike} target Target.
*/
export function unlistenAll(target) {
const listenerMap = getListenerMap(target);
if (listenerMap) {
for (const type in listenerMap) {
removeListeners(target, type);
}
}
}