dom-delegator
Version:
Decorate elements with delegated events
188 lines (144 loc) • 5.07 kB
JavaScript
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"
}