Feature / Fix: Extend Events.listens to search for specific function (#8161)

* Update Events.listens to search for specific function

Check if listener is existing in `_panInsideMaxBounds` before rm listene

revert example

Fix lint

* Refactor _listens

* Add function definition for IE

* Refactor

* Return index and remove indexOf

* _listens return number or false

* Revert to true

Co-authored-by: Volodymyr Agafonkin <agafonkin@gmail.com>
This commit is contained in:
Florian Bischof 2022-04-25 13:03:22 +02:00 committed by GitHub
parent 990d49773a
commit 0f987feda9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 40 deletions

View File

@ -659,6 +659,50 @@ describe('Events', function () {
obj.fire('test');
expect(obj.listens('test')).to.be(false);
});
it('returns true if event handler with specific function and context is existing', function () {
var obj = new L.Evented(),
differentContext = new L.Evented(),
spy = sinon.spy(),
diffentFnc = sinon.spy();
obj.on('test', spy);
// event handler 'test' is existing
expect(obj.listens('test')).to.be(true);
// event handler with specific function is existing
expect(obj.listens('test', spy)).to.be(true); // context arg: undefined === this
expect(obj.listens('test', spy, obj)).to.be(true);
// event handler with specific function and other context is not existing
expect(obj.listens('test', spy, differentContext)).to.be(false);
// event handler with specific function is not existing
expect(obj.listens('test', diffentFnc)).to.be(false);
});
it('is true if there is an event handler on parent', function () {
var fg = L.featureGroup(),
marker = L.marker([0, 0]).addTo(fg),
spy = sinon.spy();
fg.on('test', spy);
expect(marker.listens('test', false)).to.be(false);
expect(marker.listens('test', true)).to.be(true);
});
it('is true if there is an event handler with specific function on parent', function () {
var fg = L.featureGroup(),
marker = L.marker([0, 0]).addTo(fg),
spy = sinon.spy();
fg.on('test', spy);
expect(marker.listens('test', spy, marker, false)).to.be(false);
expect(marker.listens('test', spy, marker, true)).to.be(false);
expect(marker.listens('test', spy, fg, false)).to.be(false);
expect(marker.listens('test', spy, fg, true)).to.be(true);
});
});
describe('#L.Mixin.Events', function () {

View File

@ -100,30 +100,22 @@ export var Events = {
console.warn('wrong listener type: ' + typeof fn);
return;
}
this._events = this._events || {};
/* get/init listeners for type */
var typeListeners = this._events[type];
if (!typeListeners) {
typeListeners = [];
this._events[type] = typeListeners;
// check if fn already there
if (this._listens(type, fn, context) !== false) {
return;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
var newListener = {fn: fn, ctx: context},
listeners = typeListeners;
// check if fn already there
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return;
}
}
var newListener = {fn: fn, ctx: context};
listeners.push(newListener);
this._events = this._events || {};
this._events[type] = this._events[type] || [];
this._events[type].push(newListener);
},
_off: function (type, fn, context) {
@ -131,10 +123,11 @@ export var Events = {
i,
len;
if (!this._events) { return; }
if (!this._events) {
return;
}
listeners = this._events[type];
if (!listeners) {
return;
}
@ -152,30 +145,24 @@ export var Events = {
return;
}
if (context === this) {
context = undefined;
}
if (typeof fn !== 'function') {
console.warn('wrong listener type: ' + typeof fn);
return;
}
// find fn and remove it
for (i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
if (l.ctx !== context) { continue; }
if (l.fn === fn) {
if (this._firingCount) {
// set the removed listener to noop so that's not called if remove happens in fire
l.fn = Util.falseFn;
var index = this._listens(type, fn, context);
if (index !== false) {
var listener = listeners[index];
if (this._firingCount) {
// set the removed listener to noop so that's not called if remove happens in fire
listener.fn = Util.falseFn;
/* copy array in case events are being fired */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(i, 1);
return;
/* copy array in case events are being fired */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(index, 1);
return;
}
console.warn('listener not found');
},
@ -216,24 +203,61 @@ export var Events = {
},
// @method listens(type: String, propagate?: Boolean): Boolean
// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
// Returns `true` if a particular event type has any listeners attached to it.
// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
listens: function (type, propagate) {
listens: function (type, fn, context, propagate) {
if (typeof type !== 'string') {
console.warn('"string" type argument expected');
}
if (typeof fn !== 'function') {
propagate = !!fn;
fn = undefined;
context = undefined;
}
var listeners = this._events && this._events[type];
if (listeners && listeners.length) { return true; }
if (listeners && listeners.length) {
if (this._listens(type, fn, context) !== false) {
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; }
if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
}
}
return false;
},
// returns the index (number) or false
_listens: function (type, fn, context) {
if (!this._events) {
return false;
}
var listeners = this._events[type] || [];
if (!fn) {
return !!listeners.length;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return i;
}
}
return false;
},
// @method once(…): this
// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
once: function (types, fn, context) {

View File

@ -446,8 +446,7 @@ export var Map = Evented.extend({
setMaxBounds: function (bounds) {
bounds = toLatLngBounds(bounds);
if (this._maxBoundsSet) {
this._maxBoundsSet = false;
if (this.listens('moveend', this._panInsideMaxBounds)) {
this.off('moveend', this._panInsideMaxBounds);
}
@ -462,7 +461,6 @@ export var Map = Evented.extend({
this._panInsideMaxBounds();
}
this._maxBoundsSet = true;
return this.on('moveend', this._panInsideMaxBounds);
},