|
|
@@ -20,7 +20,14 @@ let eventsCounter = 0;
|
|
|
*/
|
|
|
const EmitterMixin = {
|
|
|
/**
|
|
|
- * Registers a callback function to be executed when an event is fired.
|
|
|
+ * Registers a callback function to be executed when an event is fired. Events can be grouped in namespaces using `:`.
|
|
|
+ * When namespaced event is fired, it additionaly fires all callbacks for that namespace.
|
|
|
+ *
|
|
|
+ * myEmitter.on( 'myGroup', genericCallback );
|
|
|
+ * myEmitter.on( 'myGroup:myEvent', specificCallback );
|
|
|
+ * myEmitter.fire( 'myGroup' ); // genericCallback is fired.
|
|
|
+ * myEmitter.fire( 'myGroup:myEvent' ); // both genericCallback and specificCallback are fired.
|
|
|
+ * myEmitter.fire( 'myGroup:foo' ); // genericCallback is fired even though there are no callbacks for "foo".
|
|
|
*
|
|
|
* @param {String} event The name of the event.
|
|
|
* @param {Function} callback The function to be called on event.
|
|
|
@@ -31,7 +38,8 @@ const EmitterMixin = {
|
|
|
* @method utils.EmitterMixin#on
|
|
|
*/
|
|
|
on( event, callback, ctx, priority ) {
|
|
|
- const callbacks = getCallbacks( this, event );
|
|
|
+ createEventNamespace( this, event );
|
|
|
+ const lists = getCallbacksListsForNamespace( this, event );
|
|
|
|
|
|
// Set the priority defaults.
|
|
|
if ( typeof priority != 'number' ) {
|
|
|
@@ -41,21 +49,31 @@ const EmitterMixin = {
|
|
|
callback = {
|
|
|
callback: callback,
|
|
|
ctx: ctx || this,
|
|
|
- priority: priority,
|
|
|
- // Save counter value as unique id.
|
|
|
- counter: ++eventsCounter
|
|
|
+ priority: priority
|
|
|
};
|
|
|
|
|
|
- // Add the callback to the list in the right priority position.
|
|
|
- for ( let i = callbacks.length - 1; i >= 0; i-- ) {
|
|
|
- if ( callbacks[ i ].priority <= priority ) {
|
|
|
- callbacks.splice( i + 1, 0, callback );
|
|
|
+ // Add the callback to all callbacks list.
|
|
|
+ for ( let callbacks of lists ) {
|
|
|
+ // Save counter value as unique id.
|
|
|
+ callback.counter = ++eventsCounter;
|
|
|
+
|
|
|
+ // Add the callback to the list in the right priority position.
|
|
|
+ let added = false;
|
|
|
+
|
|
|
+ for ( let i = callbacks.length - 1; i >= 0; i-- ) {
|
|
|
+ if ( callbacks[ i ].priority <= priority ) {
|
|
|
+ callbacks.splice( i + 1, 0, callback );
|
|
|
+ added = true;
|
|
|
|
|
|
- return;
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- callbacks.unshift( callback );
|
|
|
+ // Add to the beginning if right place was not found.
|
|
|
+ if ( !added ) {
|
|
|
+ callbacks.unshift( callback );
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -79,7 +97,7 @@ const EmitterMixin = {
|
|
|
callback.apply( this, arguments );
|
|
|
};
|
|
|
|
|
|
- // Make the a similar on() call, simply replacing the callback.
|
|
|
+ // Make a similar on() call, simply replacing the callback.
|
|
|
this.on( event, onceCallback, ctx, priority );
|
|
|
},
|
|
|
|
|
|
@@ -93,18 +111,16 @@ const EmitterMixin = {
|
|
|
* @method utils.EmitterMixin#off
|
|
|
*/
|
|
|
off( event, callback, ctx ) {
|
|
|
- const callbacks = getCallbacksIfAny( this, event );
|
|
|
-
|
|
|
- if ( !callbacks ) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- for ( let i = 0; i < callbacks.length; i++ ) {
|
|
|
- if ( callbacks[ i ].callback == callback ) {
|
|
|
- if ( !ctx || ctx == callbacks[ i ].ctx ) {
|
|
|
- // Remove the callback from the list (fixing the next index).
|
|
|
- callbacks.splice( i, 1 );
|
|
|
- i--;
|
|
|
+ const lists = getCallbacksListsForNamespace( this, event );
|
|
|
+
|
|
|
+ for ( let callbacks of lists ) {
|
|
|
+ for ( let i = 0; i < callbacks.length; i++ ) {
|
|
|
+ if ( callbacks[ i ].callback == callback ) {
|
|
|
+ if ( !ctx || ctx == callbacks[ i ].ctx ) {
|
|
|
+ // Remove the callback from the list (fixing the next index).
|
|
|
+ callbacks.splice( i, 1 );
|
|
|
+ i--;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -227,7 +243,7 @@ const EmitterMixin = {
|
|
|
* @method utils.EmitterMixin#fire
|
|
|
*/
|
|
|
fire( event, args ) {
|
|
|
- const callbacks = getCallbacksIfAny( this, event );
|
|
|
+ const callbacks = getCallbacksForEvent( this, event );
|
|
|
|
|
|
if ( !callbacks ) {
|
|
|
return;
|
|
|
@@ -270,7 +286,9 @@ const EmitterMixin = {
|
|
|
|
|
|
export default EmitterMixin;
|
|
|
|
|
|
-// Gets the internal events hash of a give object.
|
|
|
+// Gets the internal `_events` property of the given object.
|
|
|
+// `_events` property store all lists with callbacks for registered event names.
|
|
|
+// If there were no events registered on the object, empty `_events` object is created.
|
|
|
function getEvents( source ) {
|
|
|
if ( !source._events ) {
|
|
|
Object.defineProperty( source, '_events', {
|
|
|
@@ -281,26 +299,122 @@ function getEvents( source ) {
|
|
|
return source._events;
|
|
|
}
|
|
|
|
|
|
-// Gets the list of callbacks for a given event.
|
|
|
-function getCallbacks( source, eventName ) {
|
|
|
+// Creates event node for generic-specific events relation architecture.
|
|
|
+function makeEventNode() {
|
|
|
+ return {
|
|
|
+ callbacks: [],
|
|
|
+ childEvents: []
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// Creates an architecture for generic-specific events relation.
|
|
|
+// If needed, creates all events for given eventName, i.e. if the first registered event
|
|
|
+// is foo:bar:abc, it will create foo:bar:abc, foo:bar and foo event and tie them together.
|
|
|
+// It also copies callbacks from more generic events to more specific events when
|
|
|
+// specific events are created.
|
|
|
+function createEventNamespace( source, eventName ) {
|
|
|
const events = getEvents( source );
|
|
|
|
|
|
- if ( !events[ eventName ] ) {
|
|
|
- events[ eventName ] = [];
|
|
|
+ // First, check if the event we want to add to the structure already exists.
|
|
|
+ if ( events[ eventName ] ) {
|
|
|
+ // If it exists, we don't have to do anything.
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- return events[ eventName ];
|
|
|
+ // In other case, we have to create the structure for the event.
|
|
|
+ // Note, that we might need to create intermediate events too.
|
|
|
+ // I.e. if foo:bar:abc is being registered and we only have foo in the structure,
|
|
|
+ // we need to also register foo:bar.
|
|
|
+
|
|
|
+ // Currently processed event name.
|
|
|
+ let name = eventName;
|
|
|
+ // Name of the event that is a child event for currently processed event.
|
|
|
+ let childEventName = null;
|
|
|
+
|
|
|
+ // Array containing all newly created specific events.
|
|
|
+ const newEventNodes = [];
|
|
|
+
|
|
|
+ // While loop can't check for ':' index because we have to handle generic events too.
|
|
|
+ // In each loop, we truncate event name, going from the most specific name to the generic one.
|
|
|
+ // I.e. foo:bar:abc -> foo:bar -> foo.
|
|
|
+ while ( name !== '' ) {
|
|
|
+ if ( events[ name ] ) {
|
|
|
+ // If the currently processed event name is already registered, we can be sure
|
|
|
+ // that it already has all the structure created, so we can break the loop here
|
|
|
+ // as no more events need to be registered.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If this event is not yet registered, create a new object for it.
|
|
|
+ events[ name ] = makeEventNode();
|
|
|
+ // Add it to the array with newly created events.
|
|
|
+ newEventNodes.push( events[ name ] );
|
|
|
+
|
|
|
+ // Add previously processed event name as a child of this event.
|
|
|
+ if ( childEventName ) {
|
|
|
+ events[ name ].childEvents.push( childEventName );
|
|
|
+ }
|
|
|
+
|
|
|
+ childEventName = name;
|
|
|
+ // If `.lastIndexOf()` returns -1, `.substr()` will return '' which will break the loop.
|
|
|
+ name = name.substr( 0, name.lastIndexOf( ':' ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( name !== '' ) {
|
|
|
+ // If name is not empty, we found an already registered event that was a parent of the
|
|
|
+ // event we wanted to register.
|
|
|
+
|
|
|
+ // Copy that event's callbacks to newly registered events.
|
|
|
+ for ( let node of newEventNodes ) {
|
|
|
+ node.callbacks = events[ name ].callbacks.slice();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add last newly created event to the already registered event.
|
|
|
+ events[ name ].childEvents.push( childEventName );
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-// Get the list of callbacks for a given event only if there is any available.
|
|
|
-function getCallbacksIfAny( source, event ) {
|
|
|
- let callbacks;
|
|
|
+// Gets an array containing callbacks list for a given event and it's more specific events.
|
|
|
+// I.e. if given event is foo:bar and there is also foo:bar:abc event registered, this will
|
|
|
+// return callback list of foo:bar and foo:bar:abc (but not foo).
|
|
|
+// Returns empty array if given event has not been yet registered.
|
|
|
+function getCallbacksListsForNamespace( source, eventName ) {
|
|
|
+ const eventNode = getEvents( source )[ eventName ];
|
|
|
|
|
|
- if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
|
|
|
- return null;
|
|
|
+ if ( !eventNode ) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ let callbacksLists = [ eventNode.callbacks ];
|
|
|
+
|
|
|
+ for ( let i = 0; i < eventNode.childEvents.length; i++ ) {
|
|
|
+ let childCallbacksLists = getCallbacksListsForNamespace( source, eventNode.childEvents[ i ] );
|
|
|
+
|
|
|
+ callbacksLists = callbacksLists.concat( childCallbacksLists );
|
|
|
+ }
|
|
|
+
|
|
|
+ return callbacksLists;
|
|
|
+}
|
|
|
+
|
|
|
+// Get the list of callbacks for a given event, but only if there any callbacks have been registered.
|
|
|
+// If there are no callbacks registered for given event, it checks if this is a specific event and looks
|
|
|
+// for callbacks for it's more generic version.
|
|
|
+function getCallbacksForEvent( source, eventName ) {
|
|
|
+ let event;
|
|
|
+
|
|
|
+ if ( !source._events || !( event = source._events[ eventName ] ) || !event.callbacks.length ) {
|
|
|
+ // There are no callbacks registered for specified eventName.
|
|
|
+ // But this could be a specific-type event that is in a namespace.
|
|
|
+ if ( eventName.indexOf( ':' ) > -1 ) {
|
|
|
+ // If the eventName is specific, try to find callback lists for more generic event.
|
|
|
+ return getCallbacksForEvent( source, eventName.substr( 0, eventName.indexOf( ':' ) ) );
|
|
|
+ } else {
|
|
|
+ // If this is a top-level generic event, return null;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return callbacks;
|
|
|
+ return event.callbacks;
|
|
|
}
|
|
|
|
|
|
/**
|