UNPKG

@type-r/mixture

Version:

React-style mixins, Backbone-style events, logging router.

203 lines (158 loc) 6.86 kB
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. */ @define @definitions({ properties : mixinRules.merge, localEvents : mixinRules.merge }) 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; }