@type-r/mixture
Version:
React-style mixins, Backbone-style events, logging router.
250 lines (208 loc) • 7.29 kB
text/typescript
import { once as _once } from './tools'
/*******************
* Prebuilt events map, used for optimized bulk event subscriptions.
*
* const events = new EventMap({
* 'change' : true, // Resend this event from self as it is.
* 'change:attr' : 'localTargetFunction',
* 'executedInTargetContext' : function(){ ... }
* 'executedInNativeContext' : '^props.handler'
* })
*/
export interface EventsDefinition {
[ events : string ] : Function | string | boolean
}
export class EventMap {
handlers : EventDescriptor[] = [];
constructor( map? : EventsDefinition | EventMap ){
if( map ){
if( map instanceof EventMap ){
this.handlers = map.handlers.slice();
}
else{
map && this.addEventsMap( map );
}
}
}
merge( map : EventMap ){
this.handlers = this.handlers.concat( map.handlers );
}
addEventsMap( map : EventsDefinition ){
for( let names in map ){
this.addEvent( names, map[ names ] )
}
}
bubbleEvents( names : string ){
for( let name of names.split( eventSplitter ) ){
this.addEvent( name, getBubblingHandler( name ) );
}
}
addEvent( names : string, callback : Function | string | boolean ){
const { handlers } = this;
for( let name of names.split( eventSplitter ) ){
handlers.push( new EventDescriptor( name, callback ) );
}
}
subscribe( target : {}, source : EventSource ){
for( let event of this.handlers ){
on( source, event.name, event.callback, target );
}
}
unsubscribe( target : {}, source : EventSource ){
for( let event of this.handlers ){
off( source, event.name, event.callback, target );
}
}
}
export class EventDescriptor {
callback : Function
constructor(
public name : string,
callback : Function | string | boolean
){
if( callback === true ){
this.callback = getBubblingHandler( name );
}
else if( typeof callback === 'string' ){
this.callback =
function localCallback(){
const handler = this[ callback ];
handler && handler.apply( this, arguments );
};
}
else{
this.callback = <Function>callback;
}
}
}
const _bubblingHandlers = {};
function getBubblingHandler( event : string ){
return _bubblingHandlers[ event ] || (
_bubblingHandlers[ event ] = function( a?, b?, c?, d?, e? ){
if( d !== void 0 || e !== void 0 ) trigger5( this, event, a, b, c, d, e );
if( c !== void 0 ) trigger3( this, event, a, b, c );
else trigger2( this, event, a, b );
}
);
}
export interface HandlersByEvent {
[ name : string ] : EventHandler
}
export class EventHandler {
constructor( public callback : Callback, public context : any, public next = null ){}
}
function listOff( _events : HandlersByEvent, name : string, callback : Callback, context : any ){
const head = _events[ name ];
let filteredHead, prev;
for( let ev = head; ev; ev = ev.next ){
// Element must be kept
if( ( callback && callback !== ev.callback && callback !== ev.callback._callback ) ||
( context && context !== ev.context ) ){
prev = ev;
filteredHead || ( filteredHead = ev );
}
// Element must be skipped
else{
if( prev ) prev.next = ev.next;
}
}
if( head !== filteredHead ) _events[ name ] = filteredHead;
}
function listSend2( head : EventHandler, a, b ){
for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b );
}
function listSend3( head : EventHandler, a, b, c ){
for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c );
}
function listSend4( head : EventHandler, a, b, c, d ){
for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d );
}
function listSend5( head : EventHandler, a, b, c, d, e ){
for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d, e );
}
function listSend6( head : EventHandler, a, b, c, d, e, f ){
for( let ev = head; ev; ev = ev.next ) ev.callback.call( ev.context, a, b, c, d, e, f );
}
export interface Callback extends Function {
_callback? : Function
}
export function on( source : EventSource, name : string, callback : Callback, context? : any ) : void {
if( callback ){
const _events = source._events || ( source._events = Object.create( null ) );
_events[ name ] = new EventHandler( callback, context, _events[ name ] );
}
}
export function once( source : EventSource, name : string, callback : Callback, context? : any ) : void {
if( callback ){
const once : Callback = _once( function(){
off( source, name, once );
callback.apply(this, arguments);
});
once._callback = callback;
on( source, name, once, context );
}
}
export function off( source : EventSource, name? : string, callback? : Callback, context? : any ) : void {
const { _events } = source;
if( _events ){
if( callback || context ) {
if( name ){
listOff( _events, name, callback, context );
}
else{
for( let name in _events ){
listOff( _events, name, callback, context );
}
}
}
else if( name ){
_events[ name ] = void 0;
}
else{
source._events = void 0;
}
}
}
export interface EventSource {
/** @internal */
_events : HandlersByEvent
}
const eventSplitter = /\s+/;
export function strings( api : ApiEntry, source : EventSource, events : string, callback : Callback, context ){
if( eventSplitter.test( events ) ){
const names = events.split( eventSplitter );
for( let name of names ) api( source, name, callback, context );
}
else api( source, events, callback, context );
}
export type ApiEntry = ( source : EventSource, event : string, callback : Callback, context? : any ) => void
/*********************************
* Event-triggering API
*/
export function trigger2( self : EventSource, name : string, a, b ) : void {
const { _events } = self;
if( _events ){
const queue = _events[ name ],
{ all } = _events;
listSend2( queue, a, b );
listSend3( all, name, a, b );
}
};
export function trigger3( self : EventSource, name : string, a, b, c ) : void{
const { _events } = self;
if( _events ){
const queue = _events[ name ],
{ all } = _events;
listSend3( queue, a, b, c );
listSend4( all, name, a, b, c );
}
};
export function trigger5( self : EventSource, name : string, a, b, c, d, e ) : void{
const { _events } = self;
if( _events ){
const queue = _events[ name ],
{ all } = _events;
listSend5( queue, a, b, c, d, e );
listSend6( all, name, a, b, c, d, e );
}
};