-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Closed
Labels
Out of ScopeThis idea sits outside of the TypeScript language design constraintsThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScriptAn idea for TypeScript
Description
Search Terms
event, delegate.
Suggestion
Add the keyword "event", and make the event an explicit member of the class. Thus, you can check the event listeners, the correctness of the arguments when emitting events and etc.
Use Cases
Problems
- There is no validation check of the event name when emitting, adding and deleting a listeners
- There is no check of sent arguments when emitting an event
- No validation of listeners arguments
- Documentation generators cannot pull the list of class events
- You cannot inherit EventEmitter methods if my class must inherit from another class that is not ancestor of EventEmitter
- If you need a reaction to add and remove listeners, you have to write one big function in which the necessary events are filtered
- If you build events on decorators or getters, then a large number of objects are created that are not used
Examples
"event" keyword full semantic
class ClassWithEvents {
//declare a simple event without arguments
public event simpleEvent;
//declare a simple event with arguments
public event simpleEventWithArguments(arg: Arg1Type);
//declare a static event with reaction and arguments
public static event staticEventWithReaction(arg1: Arg1Type, arg2: Arg2Type) {
//The first argument of the listeners is always the sender of the event
add(listener: (sender: this, arg1: Arg1Type, arg2: Arg2Type) => void) {
//Do something when adding a listener
}
remove(listener: (sender: this, arg1: Arg1Type, arg2: Arg2Type) => void) {
//Do something when removing a listener
}
}
static emitEvent() {
//The emitter sends only arguments
//the "sender" parameter is sent by the system automatically.
this.staticEventWithReaction(new Arg1Type , new Arg2Type);
}
}
//Event interface
interface Event<SenderT, Arg1T, Arg2T/*etc*/> {
//Method adds/remove a listener
on(listener: (sender: SenderT, arg1: Arg1T, arg2: Arg2T) => void);
once(listener: (sender: SenderT, arg1: Arg1T, arg2: Arg2T) => void);
remove(listener: (sender: SenderT, arg1: Arg1T, arg2: Arg2T) => void);
//Method adds/remove a listener and binds context to it
on(context: any, listener: (sender: SenderT, arg1: Arg1T, arg2: Arg2T) => void);
once(context: any, listener: (sender: SenderT, arg1: Arg1T, arg2: Arg2T) => void);
//Method removes all event listeners
removeAll();
//Method emit event
emit(arg1: Arg1T, arg2: Arg2T);
//Method removes all listeners to all events
static removeAll(sender: SenderT);
}
//Use events
const myContext = {};
const listener = (sender: ClassWithEvents, arg1: Arg1Type, arg2: Arg2Type) => {
};
//add/remove listener
ClassWithEvents.staticEventWithReaction.on(listener);
ClassWithEvents.staticEventWithReaction.remove(listener);
//add/remove listener with manual context binding
const listenerWithContext = listener.bind(myContext);
ClassWithEvents.staticEventWithReaction.on(listenerWithContext);
ClassWithEvents.staticEventWithReaction.remove(listenerWithContext);
//add listener with auto context binding
ClassWithEvents.staticEventWithReaction.on(myContext, listener);
//removes all event listeners
ClassWithEvents.staticEventWithReaction.removeAll();
//removes all listeners to all events
Event.removeAll(ClassWithEvents);Tslib helpers
function __eventAddListener(target, eventName, listener, context, once) {
if( typeof context !== "undefined" ) {
listener = listener.bind(context);
}
var events = target['__tsevents'];
if( !events ) {
events = target['__tsevents'] = {};
}
var event = events[eventName];
if( !event ) {
event = events[eventName] = [];
}
event.push({
listener: listener,
once: once || false
});
__eventAddListenerTrigger(target, eventName, listener);
}
function __eventRemoveListener(target, eventName?, listener?) {
var events = target['__tsevents'];
if( !events ) {
return;
}
else if( typeof eventName === "undefined" ) {
__eventRemoveListenerTrigger(target);
target['__tsevents'] = null;
return;
}
var event = events[eventName];
if( !event ) {
return;
}
else if( typeof listener === "undefined" ) {
__eventRemoveListenerTrigger(target, eventName);
events[eventName] = null;
return;
}
events[eventName] = event.filter(function(event) {
var isEquals = event.listener === listener;
__eventRemoveListenerTrigger(target, eventName, listener);
return !isEquals;
});
}
function __eventDeclareTrigger(target, eventName, trigger) {
var triggers = target['__tseventstriggers'];
if( !triggers ) {
triggers = target['__tseventstriggers'] = {};
}
triggers[eventName] = trigger;
}
function __eventRemoveListenerTrigger(target, eventName?, listener?) {
var triggers = target['__tseventstriggers'];
if( !triggers ) {
return;
}
else if( typeof eventName === "undefined" ) {
Object.keys(triggers).forEach(eventName => {
if( target['__tsevents'] && target['__tsevents'][eventName] ) {
target['__tsevents'][eventName].forEach(event => {
triggers[eventName].remove.call(target, event.listener)
});
}
});
return;
}
var trigger = triggers[eventName];
if( !triggers ) {
return;
}
else if( typeof listener === "undefined" ) {
if( target['__tsevents'] && target['__tsevents'][eventName] ) {
target['__tsevents'][eventName].forEach(event => {
trigger.remove.call(target, event.listener)
});
}
return;
}
if( target['__tsevents'] && target['__tsevents'][eventName] ) {
trigger.remove.call(target, listener);
}
}
function __eventAddListenerTrigger(target, eventName, listener) {
var triggers = target['__tseventstriggers'];
if( !triggers ) {
return;
}
var trigger = triggers[eventName];
if( !triggers ) {
return;
}
trigger.add.call(target, listener);
}
function __eventEmit(sender, eventName, ...args) {
var events = sender['__tsevents'];
if( !events ) {
return;
}
var event = events[eventName];
if( !event ) {
return;
}
event.forEach(event => {
event.listener(sender, ...args);
if( event.once ) {
__eventRemoveListener(sender, eventName, event.listener);
}
})
}Compile result
var ClassWithEvents = (function () {
function ClassWithEvents() {
}
ClassWithEvents.emitEvent = function () {
//The emiter sends only arguments
//the "sender" parameter is sent by the system automatically.
__eventEmit(this, 'staticEventWithReaction', new Arg1Type, new Arg2Type);
};
__eventDeclareTrigger(ClassWithEvents, 'staticEventWithReaction', {
add: function add() {
//Do something when adding a listener
},
remove: function remove() {
//Do something when removing a listener
}
})
return ClassWithEvents;
}());
//Use events
var myContext = {};
var listener = function (sender, arg1, arg2) {
};
//add/remove listener
__eventAddListener(ClassWithEvents, 'staticEventWithReaction', listener);
__eventRemoveListener(ClassWithEvents, 'staticEventWithReaction', listener);
//add/remove listener with manual context binding
var listenerWithContext = listener.bind(myContext);
__eventAddListener(ClassWithEvents, 'staticEventWithReaction', listenerWithContext);
__eventRemoveListener(ClassWithEvents, 'staticEventWithReaction', listenerWithContext);
//add listener with auto context binding
__eventAddListener(ClassWithEvents, 'staticEventWithReaction', listener, myContext);
//removes all event listeners
__eventRemoveListener(ClassWithEvents, 'staticEventWithReaction');
//removes all listeners to all events
__eventRemoveListener(ClassWithEvents);Checklist
My suggestion meets these guidelines:
- [ x ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [ x ] This wouldn't change the runtime behavior of existing JavaScript code
- [ x ] This could be implemented without emitting different JS based on the types of the expressions
- [ x ] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- [ x ] This feature would agree with the rest of TypeScript's Design Goals.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Out of ScopeThis idea sits outside of the TypeScript language design constraintsThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScriptAn idea for TypeScript