UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

229 lines (178 loc) 5.93 kB
/* * 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};