Added Evented#unhookEvent for logic overide
parent
701a49226c
commit
914e4eed78
|
@ -259,6 +259,8 @@ module.exports = Trait( 'Evented' )
|
||||||
* (by not forwarding the listener to __supet) and instead invoke
|
* (by not forwarding the listener to __supet) and instead invoke
|
||||||
* __super with a new listener, possibly wrapping the original.
|
* __super with a new listener, possibly wrapping the original.
|
||||||
*
|
*
|
||||||
|
* See also #unhookEvent.
|
||||||
|
*
|
||||||
* @O {1} constant time
|
* @O {1} constant time
|
||||||
*
|
*
|
||||||
* @param {string} ev defined event id
|
* @param {string} ev defined event id
|
||||||
|
@ -328,17 +330,18 @@ module.exports = Trait( 'Evented' )
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a previously hooked listener in constant time, preventing it
|
* Remove a previously hooked listener, preventing it from being invoked
|
||||||
* from being invoked on future emits of event EV
|
* on future emits of event EV
|
||||||
*
|
*
|
||||||
* EV must be a valid event identifier. If LISTENER is not registered
|
* EV must be a valid event identifier. If LISTENER is not registered
|
||||||
* for event id EV, no error will occur (since it fulfills the criteria
|
* for event id EV, no error will occur (since it fulfills the criteria
|
||||||
* that it will not be subsequently invoked by event EV).
|
* that it will not be subsequently invoked by event EV).
|
||||||
*
|
*
|
||||||
* Listeners are compared by reference---the exact listener that was
|
* The default algorithm compares listeners by reference---the exact
|
||||||
* registered with EV must be passed for removal.
|
* listener that was registered with EV must be passed for removal;
|
||||||
|
* note, however, that the algorithm may be overridden by subtypes.
|
||||||
*
|
*
|
||||||
* @O {1} constant time
|
* @O {#unhookEvent} depends on unhooking algorithm
|
||||||
*
|
*
|
||||||
* @param {string} ev defined event id
|
* @param {string} ev defined event id
|
||||||
* @param {Function} listener listener to remove
|
* @param {Function} listener listener to remove
|
||||||
|
@ -353,6 +356,38 @@ module.exports = Trait( 'Evented' )
|
||||||
throw Error( `Cannot unhook undefined event \`${ev}'` );
|
throw Error( `Cannot unhook undefined event \`${ev}'` );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.unhookEvent( ev, listener );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a previously hooked listener in constant time, preventing it
|
||||||
|
* from being invoked on future emits of event EV
|
||||||
|
*
|
||||||
|
* Listeners are compared by reference---the exact listener that was
|
||||||
|
* registered with EV must be passed for removal.
|
||||||
|
*
|
||||||
|
* Subtypes may override this method to undo behavior introduced by a
|
||||||
|
* #hookEvent override. Note, however, that the Eventable specification
|
||||||
|
* prohibits removing arbitrary listeners, and so the removed
|
||||||
|
* listener(s) must have somehow been related to LISTENER by #hookEvent.
|
||||||
|
*
|
||||||
|
* This method will be called even if LISTENER has never been attached
|
||||||
|
* to event EV---this method performs that check, ensuring that any
|
||||||
|
* manipulations performed by #hookEvent can be properly handled here.
|
||||||
|
*
|
||||||
|
* See also #hookEvent.
|
||||||
|
*
|
||||||
|
* @O {1} constant time
|
||||||
|
*
|
||||||
|
* @param {string} ev defined event id
|
||||||
|
* @param {Function} listener listener to remove
|
||||||
|
*
|
||||||
|
* @return {Evented} self
|
||||||
|
*/
|
||||||
|
'virtual protected unhookEvent'( ev, listener )
|
||||||
|
{
|
||||||
let evls = this._events[ ev ],
|
let evls = this._events[ ev ],
|
||||||
levdata = listener[ this._evid ] || {},
|
levdata = listener[ this._evid ] || {},
|
||||||
index = levdata[ ev ];
|
index = levdata[ ev ];
|
||||||
|
|
|
@ -617,6 +617,109 @@ describe( 'event.Evented', () =>
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intended to clean up after #hookEvent
|
||||||
|
*/
|
||||||
|
describe( '#unhookEvent', function()
|
||||||
|
{
|
||||||
|
var ev = 'foo',
|
||||||
|
f = ()=>{};
|
||||||
|
|
||||||
|
it( 'can be overridden by subtypes', ( done ) =>
|
||||||
|
{
|
||||||
|
common.checkOverride( EvStub, 'unhookEvent',
|
||||||
|
( given_ev, given_listener ) =>
|
||||||
|
{
|
||||||
|
expect( given_ev ).to.equal( ev );
|
||||||
|
expect( given_listener ).to.equal( f );
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
)().evDefineEvents( [ ev ] )
|
||||||
|
.on( ev, f )
|
||||||
|
.removeListener( ev, f );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since #hookEvent can do whatever it pleases with listeners
|
||||||
|
* (including preventing them from being hooked to begin with, or
|
||||||
|
* replacing them with another hook), we need to ensure that the
|
||||||
|
* hook is called regardless of whether the listener is known to be
|
||||||
|
* attached to a particular event; otherwise, certain types of
|
||||||
|
* cleanup would not be possible.
|
||||||
|
*/
|
||||||
|
it( 'will be called even if listener is not attached', ( done ) =>
|
||||||
|
{
|
||||||
|
common.checkOverride( EvStub, 'unhookEvent', ( _, __ ) =>
|
||||||
|
{
|
||||||
|
done();
|
||||||
|
} )().evDefineEvents( [ ev ] )
|
||||||
|
.removeListener( ev, f );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should never be done---since it violates the Eventable
|
||||||
|
* specification---but it is useful for determining the scope of the
|
||||||
|
* hook's ability, and is important for proving that, when we modify
|
||||||
|
* the behavior, we know that the default behavior is suppressed
|
||||||
|
* (unless we invoke it ourselves).
|
||||||
|
*/
|
||||||
|
it( 'can prevent default implementation', () =>
|
||||||
|
{
|
||||||
|
let norm = () => called = true,
|
||||||
|
called = false;
|
||||||
|
|
||||||
|
EvStub.extend(
|
||||||
|
{
|
||||||
|
'override unhookEvent': ( _, __ ) => {},
|
||||||
|
} )().evDefineEvents( [ ev ] )
|
||||||
|
.on( ev, norm )
|
||||||
|
.removeListener( ev, norm )
|
||||||
|
.evEmit( ev );
|
||||||
|
|
||||||
|
// should have been called since we suppressed unhooking
|
||||||
|
expect( called ).to.be.true;
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners must ultimately be added/removed by invoking the super
|
||||||
|
* method (to add listeners to the internal data structures needed
|
||||||
|
* for dispatch on emit). If we are doing any sort of meaningful
|
||||||
|
* manipulation, then we must be able to wholly override the
|
||||||
|
* arguments provided to us.
|
||||||
|
*
|
||||||
|
* As an example, if #hookEvent wrapped the given listener $L$ as
|
||||||
|
* $L'$, which was then hooked for dispatch on some event, then we'd
|
||||||
|
* need to similarily remove $L'$---not $L$---when invoking
|
||||||
|
* #removeListener with $L$.
|
||||||
|
*/
|
||||||
|
it( 'can override provided event id and listener', () =>
|
||||||
|
{
|
||||||
|
let rmf = () => called = true,
|
||||||
|
called = false,
|
||||||
|
ev2 = 'snazzlepuss';
|
||||||
|
|
||||||
|
EvStub.extend(
|
||||||
|
{
|
||||||
|
'override unhookEvent'( _, __ )
|
||||||
|
{
|
||||||
|
// use these regardless of what we're given
|
||||||
|
this.__super( ev, rmf );
|
||||||
|
},
|
||||||
|
} )().evDefineEvents( [ ev, ev2 ] )
|
||||||
|
.on( ev, rmf )
|
||||||
|
.removeListener( ev2, f )
|
||||||
|
.evEmit( ev );
|
||||||
|
|
||||||
|
// even though we requested to remove F from some arbitrary
|
||||||
|
// event, our hard-coded removal of RMF should take place
|
||||||
|
expect( called ).to.be.false;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
describe( 'listener metadata', () =>
|
describe( 'listener metadata', () =>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue