cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
241 lines (187 loc) • 6.56 kB
JavaScript
import * as util from './util';
import * as is from './is';
import Event from './event';
const eventRegex = /^([^.]+)(\.(?:[^.]+))?$/; // regex for matching event strings (e.g. "click.namespace")
const universalNamespace = '.*'; // matches as if no namespace specified and prevents users from unbinding accidentally
const defaults = {
qualifierCompare: function( q1, q2 ){
return q1 === q2;
},
eventMatches: function( /*context, listener, eventObj*/ ){
return true;
},
addEventFields: function( /*context, evt*/ ){
},
callbackContext: function( context/*, listener, eventObj*/ ){
return context;
},
beforeEmit: function(/* context, listener, eventObj */){
},
afterEmit: function(/* context, listener, eventObj */){
},
bubble: function( /*context*/ ){
return false;
},
parent: function( /*context*/ ){
return null;
},
context: null
};
let defaultsKeys = Object.keys( defaults );
let emptyOpts = {};
function Emitter( opts = emptyOpts, context ){
// micro-optimisation vs Object.assign() -- reduces Element instantiation time
for( let i = 0; i < defaultsKeys.length; i++ ){
let key = defaultsKeys[i];
this[key] = opts[key] || defaults[key];
}
this.context = context || this.context;
this.listeners = [];
this.emitting = 0;
}
let p = Emitter.prototype;
let forEachEvent = function( self, handler, events, qualifier, callback, conf, confOverrides ){
if( is.fn( qualifier ) ){
callback = qualifier;
qualifier = null;
}
if( confOverrides ){
if( conf == null ){
conf = confOverrides;
} else {
conf = util.assign( {}, conf, confOverrides );
}
}
let eventList = is.array(events) ? events : events.split(/\s+/);
for( let i = 0; i < eventList.length; i++ ){
let evt = eventList[i];
if( is.emptyString( evt ) ){ continue; }
let match = evt.match( eventRegex ); // type[.namespace]
if( match ){
let type = match[1];
let namespace = match[2] ? match[2] : null;
let ret = handler( self, evt, type, namespace, qualifier, callback, conf );
if( ret === false ){ break; } // allow exiting early
}
}
};
let makeEventObj = function( self, obj ){
self.addEventFields( self.context, obj );
return new Event( obj.type, obj );
};
let forEachEventObj = function( self, handler, events ){
if( is.event( events ) ){
handler( self, events );
return;
} else if( is.plainObject( events ) ){
handler( self, makeEventObj( self, events ) );
return;
}
let eventList = is.array(events) ? events : events.split(/\s+/);
for( let i = 0; i < eventList.length; i++ ){
let evt = eventList[i];
if( is.emptyString( evt ) ){ continue; }
let match = evt.match( eventRegex ); // type[.namespace]
if( match ){
let type = match[1];
let namespace = match[2] ? match[2] : null;
let eventObj = makeEventObj( self, {
type: type,
namespace: namespace,
target: self.context
} );
handler( self, eventObj );
}
}
};
p.on = p.addListener = function( events, qualifier, callback, conf, confOverrides ){
forEachEvent( this, function( self, event, type, namespace, qualifier, callback, conf ){
if( is.fn( callback ) ){
self.listeners.push( {
event: event, // full event string
callback: callback, // callback to run
type: type, // the event type (e.g. 'click')
namespace: namespace, // the event namespace (e.g. ".foo")
qualifier: qualifier, // a restriction on whether to match this emitter
conf: conf // additional configuration
} );
}
}, events, qualifier, callback, conf, confOverrides );
return this;
};
p.one = function( events, qualifier, callback, conf ){
return this.on( events, qualifier, callback, conf, { one: true } );
};
p.removeListener = p.off = function( events, qualifier, callback, conf ){
if( this.emitting !== 0 ){
this.listeners = util.copyArray( this.listeners );
}
let listeners = this.listeners;
for( let i = listeners.length - 1; i >= 0; i-- ){
let listener = listeners[i];
forEachEvent( this, function( self, event, type, namespace, qualifier, callback/*, conf*/ ){
if(
( listener.type === type || events === '*' ) &&
( (!namespace && listener.namespace !== '.*') || listener.namespace === namespace ) &&
( !qualifier || self.qualifierCompare( listener.qualifier, qualifier ) ) &&
( !callback || listener.callback === callback )
){
listeners.splice( i, 1 );
return false;
}
}, events, qualifier, callback, conf );
}
return this;
};
p.removeAllListeners = function(){
return this.removeListener('*');
};
p.emit = p.trigger = function( events, extraParams, manualCallback ){
let listeners = this.listeners;
let numListenersBeforeEmit = listeners.length;
this.emitting++;
if( !is.array( extraParams ) ){
extraParams = [ extraParams ];
}
forEachEventObj( this, function( self, eventObj ){
if( manualCallback != null ){
listeners = [{
event: eventObj.event,
type: eventObj.type,
namespace: eventObj.namespace,
callback: manualCallback
}];
numListenersBeforeEmit = listeners.length;
}
for( let i = 0; i < numListenersBeforeEmit; i++ ){
let listener = listeners[i];
if(
( listener.type === eventObj.type ) &&
( !listener.namespace || listener.namespace === eventObj.namespace || listener.namespace === universalNamespace ) &&
( self.eventMatches( self.context, listener, eventObj ) )
){
let args = [ eventObj ];
if( extraParams != null ){
util.push( args, extraParams );
}
self.beforeEmit( self.context, listener, eventObj );
if( listener.conf && listener.conf.one ){
self.listeners = self.listeners.filter( l => l !== listener );
}
let context = self.callbackContext( self.context, listener, eventObj );
let ret = listener.callback.apply( context, args );
self.afterEmit( self.context, listener, eventObj );
if( ret === false ){
eventObj.stopPropagation();
eventObj.preventDefault();
}
} // if listener matches
} // for listener
if( self.bubble( self.context ) && !eventObj.isPropagationStopped() ){
self.parent( self.context ).emit( eventObj, extraParams );
}
}, events );
this.emitting--;
return this;
};
export default Emitter;