There are several answers for how to pass an "artificial" event in javascript.
What I dont understand is how is the event passed to the method AND event if not passed be accessible by directly using the "event" variable even though it doesnt seem to be declared anywhere.
Can someone explain this?
Thanks in advance
Here is a rudimentary event listener:
class EventCaller {
constructor() {
this.listeners = [];
}
addListener(obj) {
if (obj.trigger) { this.listeners.push(obj); }
}
trigger(event, ..data) {
this.listeners.forEach((listener) => listener.trigger(event, ...data));
}
}
class EventListener {
constructor() { this.eventsListeningTo = []; }
listenTo(item) {
if (!item.addListener) { throw new Error('Cannot add listener error'); }
item.addListener(this);
}
trigger(event, ...data) {
if (this[`on${event}`]) {this[`on${event}`](...data)}
}
onFoo({bar, foo}) {
console.log(bar, foo);
}
}
Then as an example you can create an EventCaller:
const eventAgg = new EventCaller();
Then create a few event listeners
const a = new EventListener();
const b = new EventListener();
Then a and b can listen on eventAgg events
a.listenTo(eventAgg);
b.listenTo(eventAgg);
So if now I trigger
eventAgg.trigger('Foo', {bar: 'lemons', foo: 42});
Now both a and b will console log out lemons, 42 since they are both listening on that pseudo event.
This is just basics. jquery, Backbone, angular etc all implement this in different ways (I am sure) but it all comes down to callbacks (or even Promises in newer code)
Related
I have a Javascript class, Engine, that will create new events (eg const event = new Event('build');) and then dispatch them at the appropriate time, when Intervals elapse or it catches certain events, using elem.dispatchEvent(event);
I have other classes eg MyButton that will listen for the events eg elem.addEventListener('build', myListener, false);. NOTE: MyButton is not just a button - it's a class that adds a button, listens to it and other events, and does various tasks both in response to a click and through other ways.
Main question: is there a way for the Engine class to intercept, catch or otherwise know when its event is added and removed, so that it can start and stop the Intervals etc. as needed? Basically an onAddEventListener event, or a way to achieve the same effect. I can see that it could eg have functions for adding the event listeners:
AddIntervalListener(listener) {
// check if the Interval is started, and if not then start it
if (this.waitingIntervalId == null)
this.waitingIntervalId = setInterval(catchIntervalAndFireEvent, 500);
// add the event for the caller
elem.addEventListener('build', listener, false);
}
But is there a better way so that MyButton can call addEventListener() directly, and Engine knows and can start the Interval as needed?
Minor question: Engine is not associated with a particular element in the page. Is there a best practice choice for which element in the page Engine should use to fire the events, ie the elem in elem.addEventListener()? eg window or document or something else? Why do custom events need to be dispatched to an object? says I have to but it doesn't address which to use. Or should I take its implied advice, and make my own event/listener system?
Since you are already using your own button implementations, simply implement a addEventListener() method there which raises a customEventAdded event whenever it is called:
Here's an example:
class Engine {
constructor() {
this.handleCustomEventAdded = this.handleCustomEventAdded.bind(this); // do this if you need to access the engine instance inside the listener
document.addEventListener('customEventAdded', this.handleCustomEventAdded);
}
handleCustomEventAdded(event) {
console.log(`Engine learned: a ${event.detail.element.tagName} element was added a listener for ${event.detail.name}`)
}
}
class MyButton extends HTMLButtonElement {
addEventListener(event, handler, capture) {
// pass the method call to HTMLButtonElement.prototype.addEventListener
super.addEventListener(event, handler, capture);
console.log(`Eventlistener for ${event} added`);
switch (event) {
case 'build':
let evt = new CustomEvent(
'customEventAdded', {
bubbles: true,
detail: {
name: event,
element: this
}
}
);
this.dispatchEvent(evt);
break;
default: // noop
}
}
}
customElements.define('my-button', MyButton, {
extends: 'button'
});
new Engine();
document.getElementById('foo').addEventListener('build', function(event) {
// whatever
})
<button is="my-button" id="foo">My derived button</button>
This is a very simple, inelegant solution compared to #connexo's. Still, it does what I needed. Each method has different benefits and disadvantages. I had hoped for something in between, for example where Engine's Subscriber management instead uses events, or perhaps something else; if you have such a solution, please do put it here.
class Engine {
intervalTime = 200;
intervalId = null;
subscribers = [];
constructor(intervalTime) {
if (intervalTime!==undefined)
{ // parameter not omitted in call
this.intervalTime = intervalTime;
}
this.initialise();
}
initialise() {
window.addEventListener("load", this.windowLoadEvent.bind(this));
this.intervalId = setInterval(this.doInterval.bind(this), this.intervalTime);
}
// =======================
addSubscriber(subscriber) {
// note: does not check if subscriber already subscribed
// could check here if this is the first subscriber and only then start Interval/listening to event
this.subscribers.push(subscriber);
}
removeSubscriber(subscriber) {
// could check here if this is the last subscriber and stop any Interval/listening to event
this.subscribers = this.subscribers.filter(val => val !== subscriber);
}
// =======================
windowLoadEvent() {
// do stuff
this.doEvent();
}
// =======================
doInterval() {
for (let i=0; i<this.subscribers.length; i++)
this.subscribers[i].doRegularInterval();
}
doEvent(myVariable) {
for (let i=0; i<this.subscribers.length; i++)
this.subscribers[i].doEvent(myVariable);
}
}
class ButtonManager {
myEngine = null;
constructor(myEngine) {
this.myEngine = myEngine;
this.initialise();
}
initialise() {
myEngine.addSubscriber(this);
}
// =======================
doInterval() {
// do stuff
}
doURLChanged(myVariable) {
// do stuff
}
}
let theEngine = new Engine();
let theButton = new ButtonManager(theEngine);
So im basically trying to emit and listen to a specific event on different typescript classes. The first event that is emitted is being listened properly on the other class but once I set a timeout to emit other event after 10 seconds for example, its like the listener is not listening anymore..
commonEmitter.ts
let events = require('events');
let em = new events.EventEmitter();
module.exports.commonEmitter = em;
network.ts
export class Network {
constructor() {
this.setConnection('connected');
setTimeout(() => {
commonEmitter.emit('connectionStatusChanged');
connection = 'disconnected';
}, 10000);
}
private setConnection(newConnection): void {
connection = newConnection
commonEmitter.emit('connectionStatusChanged');
}
public isConnected(): boolean {
return connection === 'connected';
}
}
export let connection = null;
view.ts
export class View {
private network: any;
constructor() { }
private test(){
console.log('Online? ' + this.network.isConnected());
}
public init(){
commonEmitter.on('connectionStatusChanged', this.test());
this.network = new Network();
}
At the end, both of the events are emitted but only the first one is being "listened".
Whats the reason for that and how I can do it in an ordered way?
In this line of code:
commonEmitter.on('connectionStatusChanged', this.test());
You're calling this.test() immediately, not passing a function reference that can be called LATER. Change it to this:
commonEmitter.on('connectionStatusChanged', this.test.bind(this));
So, you're properly passing a function reference that will also be properly bound to this.
Or, you could use a fat arrow callback function that will preserve the lexical value of this like this:
commonEmitter.on('connectionStatusChanged', () => {
this.test();
});
To answer the second question, you should've pass an object as a argument to emit
// network.ts
commonEmitter.emit('connectionStatusChanged', {data: true});
The arrow function combined with the test function call worked for me so thanks alot #jfriend000 :p The one with the bind method didn't work but it's ok.
I just have one more question, every time I try to emit and event with any type of arguments the listener is not listening to that. For example:
// view.ts
commonEmitter.on('connectionStatusChanged', (data: boolean) => {
console.log(data);
});
// network.ts
commonEmitter.emit('connectionStatusChanged', true);
If this is also related to the context, how should it be done?
By using this solution, now the first event that is emitted from the network class is never listened.. only after the timeout it works and refreshes the view but I don't know why the first one does not:/
// network.ts
commonEmitter.emit('connectionStatusChanged', {isOnline: true});
// view.ts
commonEmitter.on('connectionStatusChanged', (data: any) => {
console.log('Online? ' + data.isOnline);
});
Consider the following code:
class Test {
constructor() {
this.breakpoints = {};
}
add(options) {
// Register the media query
this.breakpoints[options.breakpoint] = window.matchMedia(options.breakpoint);
// Register the listener
this.breakpoints[options.breakpoint].addListener(this.breakpoint.bind(this));
}
remove(options) {
this.breakpoints[options.breakpoint].removeListener(this.breakpoint.bind(this));
}
breakpoint() {
// Do something...
}
}
In the above code, you will notice that I am attaching an event listener in the add method, and attempting to remove it in the remove method. Due to the code in the breakpoint method, the bind(this) part is absolutely crucial.
As a result of the bind(this) (I believe), the removeListener is not removing the media query listener. Is there any way to solve this?
I have also tried this (without the bind on remove):
remove(options) {
this.breakpoints[options.breakpoint].removeListener(this.breakpoint);
}
One option is to bind the breakpoint method to the context of the current instance in the constructor, so that referencing this.breakpoint always refers to the bound method later:
class Test {
constructor() {
this.breakpoint = this.breakpoint.bind(this);
this.breakpoints = {};
}
add(options) {
// Register the media query
this.breakpoints[options.breakpoint] = window.matchMedia(options.breakpoint);
// Register the listener
this.breakpoints[options.breakpoint].addListener(this.breakpoint);
}
remove(options) {
this.breakpoints[options.breakpoint].removeListener(this.breakpoint);
}
breakpoint() {
// Do something...
}
}
I am dynamically creating event listener for some events. In that event listener i want to do another emit call based on the event name, so i want to get the event listener name
I am using node.js eventemitter.
var events = require('events').EventEmitter;
var util = require('util');
.....
.....
for(i in events) {
transport.on(events[i],function(userId) {
eventName = events[i];
var acct = accountsList[userId];
if(acct) {
acct.emit(events[i],userId);
}
});
}
The above method is working but the problem line is
acct.emit(events[i],userId);
that events[i] is having last value of the loop. so if received any event it always emitting the final loop of the events[i] value...
So you are preserving value of event_name in a closure. It is legal, but doesn't look very neat.
Instead, you could use EventEmitter2 module like this
var EventEmitter = require('eventemitter2').EventEmitter2;
var emitter = new EventEmitter();
emitter.on('ev1', function (line) {
console.log(this.event); // here's your fired event
});
emitter.emit('ev1', 'has fired');
Check out the documentation, you could do much more than the original EventEmitter
i overcome this by function ...but i want to know this right way or not...
for(i in events) {
function test(event_name){
transport.purpleEvents.on(event_name,function(userId) {
var acct = accountsList[userId];
if(acct) {
acct.emit(event_name,userId);
}
});
}
test(events[i]);
}
The for loop you are using is async and results in all calls using the last value in events. If you replace that with forEach it will run sync. Try something like this untested code:
events.forEach(function(i) {
transport.on(events[i],function(userId) {
eventName = events[i];
var acct = accountsList[userId];
if(acct) {
acct.emit(events[i],userId);
}
});
});
you can use a closure like this which can be practical soln..
for(i in events) {
transport.on(events[i],closureplusopn(events[i]))
}
function closureplusopn(eventName){
return function(userID){
var acct = accountsList[userId];
if(acct) {
acct.emit(eventName,userID);
}
}
}
You can pass the event name through the context param.
On EventEmitter3 the .on function params are (eventName, functionName, context).
What helped me figure out how to get the eventName in the callback function was to pass this string as context and access it with the this keyword. Hope this helps whoever comes to this question and has something like this as an issue.
Suppose there are objects making subscriptions to a socket server like so:
socket.on('news', obj.socketEvent)
These objects have a short life span and are frequently created, generating many subscriptions. This seems like a memory leak and an error prone situation which would intuitively be prevented this way:
socket.off('news', obj.socketEvent)
before the object is deleted, but alas, there isn't an off method in the socket. Is there another method meant for this?
Edit: having found no answer I'm assigning a blank method to overwrite the wrapper method for the original event handler, an example follows.
var _blank = function(){};
var cbProxy = function(){
obj.socketEvent.apply(obj, arguments)
};
var cbProxyProxy = function(){
cbProxy.apply ({}, arguments)
}
socket.on('news', cbProxyProxy);
// ...and to unsubscribe
cbProxy = _blank;
From looking at the source of socket.io.js (couldn't find it in documentation anywhere), I found these two functions:
removeListener = function(name, fn)
removeAllListeners = function(name)
I used removeAllListeners successfully in my app; you should be able to choose from these:
socket.removeListener("news", cbProxy);
socket.removeAllListeners("news");
Also, I don't think your solution of cbProxy = _blank would actually work; that would only affect the cbProxy variable, not any actual socket.io event.
If you want to create listeners that "listens" only once use socket.once('news',func). Socket.io automatically will distroy the listener after the event happened - it's called "volatile listener".
Looking at the code of current version of Socket.io Client (1.4.8) it seems that off, removeAllListeners, removeEventListener are all pointing to the same function.
Calling any of those, providing event name and/or callback, gives the desired result. Not providing anything at all seems to reset everything.
Please do be cautious about the fn/callback argument. It has to be the same instance used in the code.
Example:
var eventCallback = function(data) {
// do something nice
};
socket.off('eventName', eventCallback);
Would work as expected.
Example (will also work):
function eventCallback(data) {
// do something nice
}
socket.off('eventName', eventCallback);
Please be cautious that the callback you are trying to remove is the one that you passed in (this one can bring a lot of confusion and frustration).
This example implements a wrapper around initial callback, trying to remove that would not work as the real callback being added is an undisclosed closure instance: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
Here is the link to that specific line in the codebase: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597
Socket.io version 0.9.16 implements removeListener but not off.
You can use removeListener instead of off when unsubscribing, or simply implement off as follows:
var socket = io.connect(url);
socket.off = socket.removeListener;
If you are using the Backbone listenTo event subscription approach, you'll need to implement the above as Backbone calls off when unsubscribing events.
I found that in socket.io 0.9.11 and Chrome24 socket.io removeListener doesn't work.
this modified version works for me:
EventEmitter.prototype.removeListener = function (name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (io.util.isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].toString() === fn.toString() || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else {
if (list.toString() === fn.toString() || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
}
return this;
};
Since I had a spot of troubles making this work figured I'd chime in here as well, along with a nice updated answer for 2017. Thanks to #Pjotr for pointing out that it has to be the same callback instance.
Example with Angular2 TypeScript in a socket-io.subscriber service. Note the "newCallback" wrapper
private subscriptions: Array<{
key: string,
callback: Function
}>;
constructor() {
this.subscriptions = [];
}
subscribe(key: string, callback: Function) {
let newCallback = (response) => callback(response);
this.socket.on(key, newCallback);
return this.subscriptions.push({key: key, callback: newCallback}) - 1;
}
unsubscribe(i: number) {
this.socket.removeListener(this.subscriptions[i].key, this.subscriptions[i].callback);
}
Removing an event listener on the client
var Socket = io.connect();
Socket.removeListener('test', test);
Also on java client, it can be done the same way with the Javascript client. I've pasted from socket.io.
// remove all listeners of the connect event
socket.off(Socket.EVENT_CONNECT);
listener = new Emitter.Listener() { ... };
socket.on(Socket.EVENT_CONNECT, listener);
// remove the specified listener
socket.off(Socket.EVENT_CONNECT, listener);
Pre-store the events using an array, and by the time you need to unsubscribe them, use the off method, which is a built in method from socket.io:
// init
var events = []
// store
events.push("eventName")
// subscribe
socket.on("eventName", cb)
// remove
events = events.filter(event => event!="eventName")
// unsubscribe
socket.off("eventName")
To add to #Andrew Magee, here is an example of unsubscribing socket.io events in Angular JS, and of course works with Vanilla JS:
function handleCarStarted ( data ) { // Do stuff }
function handleCarStopped ( data ) { // Do stuff }
Listen for events:
var io = $window.io(); // Probably put this in a factory, not controller instantiation
io.on('car.started', handleCarStarted);
io.on('car.stopped', handleCarStopped);
$scope.$on('$destroy', function () {
io.removeListener('car.started', handleCarStarted);
io.removeListener('car.stopped', handleCarStopped);
});
This has helped me in both Angular 8 and React 16.8:
receiveMessage() {
let newCallback = (data) => {
this.eventEmitter.emit('add-message-response', data);
};
this.socket.on('add-message-response', newCallback);
this.subscriptions.push({key: 'add-message-response', callback: newCallback});
}
receiveMessageRemoveSocketListener() {
this.findAndRemoveSocketEventListener('add-message-response');
}
findAndRemoveSocketEventListener (eventKey) {
let foundListener = this.subscriptions.find( (subscription) => subscription.key === eventKey );
if(!foundListener) {
return;
}
this.socket.removeListener(foundListener.key, foundListener.callback);
this.subscriptions = this.subscriptions.filter( (subscription) => subscription.key !== eventKey );
}
Reason for using an Array of Subscriptions is that when you Subscribe to an event multiple times and you don't remove an unsubscribed subscription from the Subscription list you will most probably be right at first time you remove the subscription from the list, but later subscriptions will not be removed as you will be finding first instance only every time you unsubscribe the event.
You can simply call receiveMessage(); to subscribe to an the event and receiveMessageRemoveSocketListener(); to Unsubscribe.