UNPKG

dom-delegator

Version:

Decorate elements with delegated events

188 lines (144 loc) 5.07 kB
var globalDocument = require("global/document") var EvStore = require("ev-store") var createStore = require("weakmap-shim/create-store") var addEvent = require("./add-event.js") var removeEvent = require("./remove-event.js") var ProxyEvent = require("./proxy-event.js") var HANDLER_STORE = createStore() module.exports = DOMDelegator function DOMDelegator(document) { if (!(this instanceof DOMDelegator)) { return new DOMDelegator(document); } document = document || globalDocument this.target = document.documentElement this.events = {} this.rawEventListeners = {} this.globalListeners = {} } DOMDelegator.prototype.addEventListener = addEvent DOMDelegator.prototype.removeEventListener = removeEvent DOMDelegator.allocateHandle = function allocateHandle(func) { var handle = new Handle() HANDLER_STORE(handle).func = func; return handle } DOMDelegator.transformHandle = function transformHandle(handle, broadcast) { var func = HANDLER_STORE(handle).func return this.allocateHandle(function (ev) { broadcast(ev, func); }) } DOMDelegator.prototype.addGlobalEventListener = function addGlobalEventListener(eventName, fn) { var listeners = this.globalListeners[eventName] || []; if (listeners.indexOf(fn) === -1) { listeners.push(fn) } this.globalListeners[eventName] = listeners; } DOMDelegator.prototype.removeGlobalEventListener = function removeGlobalEventListener(eventName, fn) { var listeners = this.globalListeners[eventName] || []; var index = listeners.indexOf(fn) if (index !== -1) { listeners.splice(index, 1) } } DOMDelegator.prototype.listenTo = function listenTo(eventName) { if (!(eventName in this.events)) { this.events[eventName] = 0; } this.events[eventName]++; if (this.events[eventName] !== 1) { return } var listener = this.rawEventListeners[eventName] if (!listener) { listener = this.rawEventListeners[eventName] = createHandler(eventName, this) } this.target.addEventListener(eventName, listener, true) } DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) { if (!(eventName in this.events)) { this.events[eventName] = 0; } if (this.events[eventName] === 0) { throw new Error("already unlistened to event."); } this.events[eventName]--; if (this.events[eventName] !== 0) { return } var listener = this.rawEventListeners[eventName] if (!listener) { throw new Error("dom-delegator#unlistenTo: cannot " + "unlisten to " + eventName) } this.target.removeEventListener(eventName, listener, true) } function createHandler(eventName, delegator) { var globalListeners = delegator.globalListeners; var delegatorTarget = delegator.target; return handler function handler(ev) { var globalHandlers = globalListeners[eventName] || [] if (globalHandlers.length > 0) { var globalEvent = new ProxyEvent(ev); globalEvent.currentTarget = delegatorTarget; callListeners(globalHandlers, globalEvent) } findAndInvokeListeners(ev.target, ev, eventName) } } function findAndInvokeListeners(elem, ev, eventName) { var listener = getListener(elem, eventName) if (listener && listener.handlers.length > 0) { var listenerEvent = new ProxyEvent(ev); listenerEvent.currentTarget = listener.currentTarget callListeners(listener.handlers, listenerEvent) if (listenerEvent._bubbles) { var nextTarget = listener.currentTarget.parentNode findAndInvokeListeners(nextTarget, ev, eventName) } } } function getListener(target, type) { // terminate recursion if parent is `null` if (target === null || typeof target === "undefined") { return null } var events = EvStore(target) // fetch list of handler fns for this event var handler = events[type] var allHandler = events.event if (!handler && !allHandler) { return getListener(target.parentNode, type) } var handlers = [].concat(handler || [], allHandler || []) return new Listener(target, handlers) } function callListeners(handlers, ev) { handlers.forEach(function (handler) { if (typeof handler === "function") { handler(ev) } else if (typeof handler.handleEvent === "function") { handler.handleEvent(ev) } else if (handler.type === "dom-delegator-handle") { HANDLER_STORE(handler).func(ev) } else { throw new Error("dom-delegator: unknown handler " + "found: " + JSON.stringify(handlers)); } }) } function Listener(target, handlers) { this.currentTarget = target this.handlers = handlers } function Handle() { this.type = "dom-delegator-handle" }