leaflet
Version:
JavaScript library for mobile-friendly interactive maps
229 lines (178 loc) • 5.93 kB
JavaScript
/*
* L.Evented is a base class that Leaflet classes inherit from to handle custom events.
*/
L.Evented = L.Class.extend({
on: function (types, fn, context) {
// types can be a map of types/handlers
if (typeof types === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn);
}
} else {
// types can be a string of space-separated words
types = L.Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context);
}
}
return this;
},
off: function (types, fn, context) {
if (!types) {
// clear all listeners if called without arguments
delete this._events;
} else if (typeof types === 'object') {
for (var type in types) {
this._off(type, types[type], fn);
}
} else {
types = L.Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._off(types[i], fn, context);
}
}
return this;
},
// attach listener (without syntactic sugar now)
_on: function (type, fn, context) {
var events = this._events = this._events || {},
contextId = context && context !== this && L.stamp(context);
if (contextId) {
// store listeners with custom context in a separate hash (if it has an id);
// gives a major performance boost when firing and removing events (e.g. on map object)
var indexKey = type + '_idx',
indexLenKey = type + '_len',
typeIndex = events[indexKey] = events[indexKey] || {},
id = L.stamp(fn) + '_' + contextId;
if (!typeIndex[id]) {
typeIndex[id] = {fn: fn, ctx: context};
// keep track of the number of keys in the index to quickly check if it's empty
events[indexLenKey] = (events[indexLenKey] || 0) + 1;
}
} else {
// individual layers mostly use "this" for context and don't fire listeners too often
// so simple array makes the memory footprint better while not degrading performance
events[type] = events[type] || [];
events[type].push({fn: fn});
}
},
_off: function (type, fn, context) {
var events = this._events,
indexKey = type + '_idx',
indexLenKey = type + '_len';
if (!events) { return; }
if (!fn) {
// clear all listeners for a type if function isn't specified
delete events[type];
delete events[indexKey];
delete events[indexLenKey];
return;
}
var contextId = context && context !== this && L.stamp(context),
listeners, i, len, listener, id;
if (contextId) {
id = L.stamp(fn) + '_' + contextId;
listeners = events[indexKey];
if (listeners && listeners[id]) {
listener = listeners[id];
delete listeners[id];
events[indexLenKey]--;
}
} else {
listeners = events[type];
if (listeners) {
for (i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn) {
listener = listeners[i];
listeners.splice(i, 1);
break;
}
}
}
}
// set the removed listener to noop so that's not called if remove happens in fire
if (listener) {
listener.fn = L.Util.falseFn;
}
},
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
var event = L.Util.extend({}, data, {type: type, target: this}),
events = this._events;
if (events) {
var typeIndex = events[type + '_idx'],
i, len, listeners, id;
if (events[type]) {
// make sure adding/removing listeners inside other listeners won't cause infinite loop
listeners = events[type].slice();
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn.call(this, event);
}
}
// fire event for the context-indexed listeners as well
for (id in typeIndex) {
typeIndex[id].fn.call(typeIndex[id].ctx, event);
}
}
if (propagate) {
// propagate the event to parents (set with addEventParent)
this._propagateEvent(event);
}
return this;
},
listens: function (type, propagate) {
var events = this._events;
if (events && (events[type] || events[type + '_len'])) { return true; }
if (propagate) {
// also check parents for listeners if event propagates
for (var id in this._eventParents) {
if (this._eventParents[id].listens(type, propagate)) { return true; }
}
}
return false;
},
once: function (types, fn, context) {
if (typeof types === 'object') {
for (var type in types) {
this.once(type, types[type], fn);
}
return this;
}
var handler = L.bind(function () {
this
.off(types, fn, context)
.off(types, handler, context);
}, this);
// add a listener that's executed once and removed after that
return this
.on(types, fn, context)
.on(types, handler, context);
},
// adds a parent to propagate events to (when you fire with true as a 3rd argument)
addEventParent: function (obj) {
this._eventParents = this._eventParents || {};
this._eventParents[L.stamp(obj)] = obj;
return this;
},
removeEventParent: function (obj) {
if (this._eventParents) {
delete this._eventParents[L.stamp(obj)];
}
return this;
},
_propagateEvent: function (e) {
for (var id in this._eventParents) {
this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
}
}
});
var proto = L.Evented.prototype;
// aliases; we should ditch those eventually
proto.addEventListener = proto.on;
proto.removeEventListener = proto.clearAllEventListeners = proto.off;
proto.addOneTimeEventListener = proto.once;
proto.fireEvent = proto.fire;
proto.hasEventListeners = proto.listens;
L.Mixin = {Events: proto};