@type-r/mixture
Version:
React-style mixins, Backbone-style events, logging router.
203 lines (158 loc) • 6.86 kB
text/typescript
import * as _eventsApi from './eventsource';
import { EventMap, EventsDefinition, EventSource, HandlersByEvent } from './eventsource';
import { define, definitions, Mixable, MixableConstructor, mixinRules, MixinsState } from './mixins';
import { omit, transform } from './tools';
const { strings, on, off, once, trigger5, trigger2, trigger3 } = _eventsApi;
let _idCount = 0;
function uniqueId() : string {
return 'l' + _idCount++;
}
export { EventMap, EventsDefinition };
export interface MessengerDefinition {
_localEvents? : EventMap
localEvents? : EventsDefinition
properties? : PropertyMap
[ name : string ] : any
}
export interface PropertyMap {
[ name : string ] : Property
}
export type Property = PropertyDescriptor | ( () => any )
export interface MessengersByCid {
[ cid : string ] : Messenger
}
export type EventCallbacks<Context> = { [ events : string ] : EventCallback<Context> }
export type EventCallback<Context> = ( this : Context, ...args : any[] ) => void
/*************************
* Messenger is mixable class with capabilities of sending and receiving synchronous events.
* This class itself can serve as both mixin and base class.
*/
export class Messenger implements Mixable, EventSource {
// Define extendable mixin static properties.
/** @internal */
static __super__ : object;
static mixins : MixinsState;
static onExtend : ( BaseClass : Function ) => void;
static define : ( definition? : MessengerDefinition, statics? : object ) => MixableConstructor;
static extend : ( definition? : MessengerDefinition, statics? : object ) => MixableConstructor;
static onDefine({ localEvents, _localEvents, properties } : MessengerDefinition, BaseClass? : typeof Mixable ){
// Handle localEvents definition
if( localEvents || _localEvents ){
const eventsMap = new EventMap( this.prototype._localEvents );
localEvents && eventsMap.addEventsMap( localEvents );
_localEvents && eventsMap.merge( _localEvents );
this.prototype._localEvents = eventsMap;
}
// Handle properties definitions...
if( properties ){
Object.defineProperties( this.prototype, transform( {}, <PropertyMap>properties, toPropertyDescriptor ) );
}
}
/** @internal */
_events : HandlersByEvent = void 0;
/** @internal */
_listeningTo : MessengersByCid = void 0
/** Unique client-only id. */
cid : string
/** Prototype-only property to manage automatic local events subscription */
protected _localEvents : EventMap
constructor(){
this.cid = uniqueId();
this.initialize.apply( this, arguments );
// TODO: local events subscribe?
}
/** Method is called at the end of the constructor */
initialize() : void {}
on( events : string | EventCallbacks<this>, callback?, context? ) : this {
if( typeof events === 'string' ) strings( on, this, events, callback, context );
else for( let name in events ) strings( on, this, name, events[ name ], context || callback );
return this;
}
once( events : string | EventCallbacks<this>, callback?, context? ) : this {
if( typeof events === 'string' ) strings( once, this, events, callback, context );
else for( let name in events ) strings( once, this, name, events[ name ], context || callback );
return this;
}
off( events? : string | EventCallbacks<this>, callback?, context? ) : this {
if( !events ) off( this, void 0, callback, context );
else if( typeof events === 'string' ) strings( off, this, events, callback, context );
else for( let name in events ) strings( off, this, name, events[ name ], context || callback );
return this;
}
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger(name : string, a?, b?, c?, d?, e? ) : this {
if( d !== void 0 || e !== void 0 ) trigger5( this, name, a, b, c, d, e );
else if( c !== void 0 ) trigger3( this, name, a, b, c );
else trigger2( this, name, a, b );
return this;
}
listenTo( source : Messenger, a : string | EventCallbacks<this>, b? : Function ) : this {
if( source ){
addReference( this, source );
source.on( a, !b && typeof a === 'object' ? this : b, this );
}
return this;
}
listenToOnce( source : Messenger, a : string | EventCallbacks<this>, b? : Function ) : this {
if( source ){
addReference( this, source );
source.once( a, !b && typeof a === 'object' ? this : b, this );
}
return this;
}
stopListening( a_source? : Messenger, a? : string | EventCallbacks<this>, b? : Function ) : this {
const { _listeningTo } = this;
if( _listeningTo ){
const removeAll = !( a || b ),
second = !b && typeof a === 'object' ? this : b;
if( a_source ){
const source = _listeningTo[ a_source.cid ];
if( source ){
if( removeAll ) delete _listeningTo[ a_source.cid ];
source.off( a, second, this );
}
}
else if( a_source == null ){
for( let cid in _listeningTo ) _listeningTo[ cid ].off( a, second, this );
if( removeAll ) ( this._listeningTo = void 0 );
}
}
return this;
}
/**
* Destructor. Stops messenger from listening to all objects,
* and stop others from listening to the messenger.
*/
_disposed : boolean
dispose() : void {
if( this._disposed ) return;
this.stopListening();
this.off();
this._disposed = true;
}
}
/**
* Backbone 1.2 API conformant Events mixin.
*/
export const Events : Messenger = <Messenger> omit( Messenger.prototype, 'constructor', 'initialize' );
/**
* Messenger Private Helpers
*/
function toPropertyDescriptor( x : Property ) : PropertyDescriptor {
if( x ){
return typeof x === 'function' ? { get : < () => any >x, configurable : true } : <PropertyDescriptor> x;
}
}
function addReference( listener : Messenger, source : Messenger ){
const listeningTo = listener._listeningTo || (listener._listeningTo = Object.create( null ) ),
cid = source.cid || ( source.cid = uniqueId() );
listeningTo[ cid ] = source;
}