Creating custom event pool in javascript - javascript

I have some client side logic (I have a little 3d world where objects interacts), and I would like to add some events listener like this:
window.addEventListener("myAmazingEvent", () => {doSomethingAmazing})
How can I implement this eventListener to my own class? Like:
class Person {
construcor() {
this.listener = new SomeJavascriptEventListener()
}
crouched() {
this.listener.call("onCrunch")
}
const a = new Person()
a.addEventListener("onCrunch", () => {a.startSinging()})
What javascript built in classes can provide this behaviour to me?

JavaScript's standard library doesn't have any direct event emitter support. There are lots of event emitter libraries you can find with a search, but none of them is built-in.
You can:
Use one of the pre-built, pre-tested event emitter libraries that already exist (look for "event emitter" or "publish/subscribe" or "pub/sub").
Use something built into the environment you're running your code in. Node.js has an EventEmitter class. On browsers, you could create a DOM element that you keep in your class instance (no need for it to be in the document) and use it to store event listeners, then use dispatchEvent to send events to it. (Though that's not really simpler than the next option.)
Build your own by maintaining an array or Set of event listeners for each event type that you loop through and call when you need to emit an event.
All three are viable options.
But to give you an idea, a class with very basic event handling looks like this:
class Example {
#eventHandlers = {
"some-event": new Set(),
"another-event": new Set(),
};
on(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.add(handler);
}
off(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.delete(handler);
}
#trigger(eventName, ...eventArgs) {
const handlers = this.#eventHandlers[eventName];
// assert(!!handlers);
for (const handler of handlers) {
try {
handler(...eventArgs);
} catch { // (Add (e) if your environment doesn't support optional catch bindings)
}
}
}
doSomething() {
// In this example, "doing something" triggers the `some-event` event
this.#trigger("some-event", "something happened");
}
}
const e = new Example();
e.on("some-event", (msg) => console.log(`Handler one: received "${msg}"`));
e.on("some-event", (msg) => console.log(`Handler two: received "${msg}"`));
e.doSomething();
That uses relatively modern things (private fields, optional catch bindings), but you can adapt it as needed.
There are a dozen or more ways to skin this cat. Cross-talk between nandlers, cancelling events, passing in some kind of Event object, etc., etc., etc. But the fundamentals are:
Registering handlers (on in the example)
Unregistering handlers (off in the example)
Triggering events (#trigger in the example)

Related

How to implement on-event handler in my javascript class (ES6)

How can I create my own "on" event handler in my class?
For example, WebSocket has multiple "on" event handler as follows.
var ws = new WebSocket("wss://localhost:1000/hoge");
ws.connect();
ws.onopen = function(e){
//TO DO once connection get established
}
ws.onmessage = function(e){
// TO DO once message comes from server sie
}
It might be silly question, but I would like to have simple sample code for the above.
Make those properties getters and setters:
class Foo {
get onsomeevent() {
return this._someeventHandler;
}
set onsomeevent(newVal) {
this._someeventHandler = newVal;
}
trigger() {
if (this._someeventHandler) {
this._someeventHandler();
}
}
}
const f = new Foo();
f.onsomeevent = () => console.log('handler running');
console.log('about to trigger');
f.trigger();
console.log(f.onsomeevent);
Let's say you wanted to have ws.ongameover and you wanted that called when the game was over.
You can simply write code like this:
// assign your function to the ws.ongameover property
ws.ongameover = function() {
console.log("game over");
}
Then, some place else in your code where you see the game is actually over, you can then trigger that like this:
// if there's a gameover handler, then call it
if (ws.ongameover) {
ws.ongameover();
}
Note, it is generally a better design pattern to use events and event handlers because that's more scalable as you can have multiple listeners for the same event and individual pieces of code can add or remove their own listeners. In the browser, they use addEventListener() and in node.js, they use the EventEmitter class interface (the two are similar in concept). With either one, you can create your own event names, code can listen for those event names and then other code can fire those events when they occur.

JavaScript: Transforming events before passing it on to a callback

I am currently implementing a Bluetooth Library for Node.js that has support for macOS, Linux and Windows. To achieve cross platform compatibility I am using native, platform specific code (Objective-C, C++ & C) that use Node.js' EventEmitter and will trigger events whenever an asynchronous Bluetooth operation has completed or some other device has triggered an event for some reason.
The data format for these events look very differently and I would like to normalize them so users of my library will get there events in one single format, not matter which platform they are on.
Of course, I can attach event handlers to the native EventEmitters, transform and normalize the data and trigger Events with that normalized data in my own library.
The problem with that is that the users of the library will be able to attach - and - remove events to my library and what that means is that if they attached an event to my library, let's say to "deviceDiscovered" like so:
const myListener = (device) => { /* ... */ };
bluetoothLibrary.on("deviceDiscovered", myListener);
My implementation would have to look a little something like this:
class BluetoothLibrary {
nativeAdapter = getAdapterForCurrentOS();
on(event, callback) {
this.nativeAdapter.on(event, (data) => {
const normalizedData = this.normalize(data)
callback(normalizedData);
});
}
off(event, callback) {
this.nativeAdapter.off(event, callback)
}
normalize(data) {
/* ... */
}
}
The problem with this is if someone wants to remove their event listeners again, like so:
const myListener = (device) => { /* ... */ };
bluetoothLibrary.on("deviceDiscovered", myListener);
bluetoothLibrary.off("deviceDiscovered", myListener);
Because of my implementation, the listener never really would be removed because I never really attach the reference to the callback that has been passed to me to the native adapter's EventEmitter.
I am wondering if there is a way to transform or pipe events through some transformations on the way before passing them on to an event listener.

Emit Event/Subscribe to a TypeScript method (regular TypeScript file) in an AngularJs TypeScript controller

How can I subscribe/or listen to an event/method in a regular TypeScript file that is part of our AngularJs TypeScript project?
Context: We have a loader, progressBar.ts with a method, updateGlobalProgressBar, that is exposed via the export attribute, that keeps track of the percentage loaded. On the other hand I have a controller, ribbonController, that sets certain properties of the ribbon view.
Is there a way I can subscribe or listen to the updateGlobalProgressBar method in my ribbonController, to know when the loader has reached 100%?
Please note: the progressBar.ts is not an AngularJs service or controller, but a plain TypeScript file with nothing being injected, no constructor method etc.
I have tried a do while loop, but this created an endless loop:
do {
this._scope.loaderHasLoaded = utilities.loadingComplete();
}
while (this._scope.loaderHasLoaded === false);
I have tried an if loop, but this only fired once, and obviously not at the right time:
if (utilities.loadingComplete()) {
this._scope.loaderHasLoaded = true;
} else {
this._scope.loaderHasLoaded = false;
}
I need to somehow listen to a variable or something in the progressBar.ts to inform me when the load is done. I wrote the following helper method that gets called when the load is complete from within the updateGlobalProgressBar:
export function loadingComplete(): boolean {
if (this.loadComplete === true) {
return true
} else {
return false;
}
}
I can call this method etc, but don't know how to subscribe to it?
Any help will be greatly appreciated!
I will attempt to help you out.
Approach:
create event emitters in progressBar.ts that emit events when the progress bar reaches 100% status.
expose the event via export attribute.
subscribe/listen to the event and perform necessary actions.
Trigger event
clean up
Now, I will elaborate with some code:
create event emitters in progressBar.ts that emit events when the progress bar reaches 100% status.
Basic event emitter class and instance creation:
class EventEmitter {
listeners=[];
register(callback) {
var _listeners = this.listeners
_listeners.push(callback);
return function() {
_listeners.splice(_listeners.indexOf(callback), 1);
}
}
trigger() {
var args =arguments;
this.listeners.forEach(callback => callback.apply(null, args));
}
}
const onProgressBarComplete = new EventEmitter();
expose the event via export attribute
export onProgressBarComplete;
subscribe/listen to the event and perform necessary actions.
import {onProgressBarComplete} from './progressBar';
var unListenToEvent = onProgressBarComplete.register(function() {
// code in response to event
});
trigger Event: In updateGlobalProgressBar method add condition
if(progress === 100) { //This condition is up to you
onProgressBarComplete.trigger();
}
clean up.
when you are no longer interested in the event remove subscirption by executing:
unListenToEvent();
Hope This points you in the right direction.
Cheers!

Remove Event listener form element in Javascript Without cloning elements and without knowing the second parameter of removeEventListener()

I searched for several questions here bt couldn't find the answer.
The accepted answer removeEventListener without knowing the function only works in chrome and that too in non strict mode.
I have three functions. One to attach event listeners to elements using selectors and another to remove event listeners from the element and the third one to remove all event listeners from the page.
The functions are something like:
function listen(node, event, callback, options)
{
node.addEventListener(event, callback, options);
}
removeAllListeners(node)
{
}
removeListener(node, event)
{
}
The user can pass any type of callback functions to the function which attaches the listener.
How do I go about removing the event listeners.
I do not want to use any third party library as well.
Tried:
var eventlistener = getEventListeners(window)["DOMContentLoaded"][index];
window.removeEventListener("DOMContentLoaded", eventlistener.listener, eventlistener.useCapture);
but only works on chrome and that too only in non strict mode.
Since you're dealing with your own event listeners, there are lots of ways you can solved this. Two that come to mind:
Use an expando property on the element to keep track of your listener functions, so you do know the second argument to removeEventListener. Be careful when using expando properties to avoid naming conflicts (e.g, choose a long name very specific to your code).
Assign elements an ID in listen and store their handlers (and the events they handle) in a separate object, keyed by that ID. Note that this means you'll keep the functions in memory as long as either the element or the entry in your separate object refers to them, which may not be ideal.
Here's an example of #1:
var handlersKey = "___handlers___" + Math.floor(Math.random() * 100000);
function listen(node, event, callback, options)
{
node.addEventListener(event, callback, options);
if (!node[handlersKey]) {
node[handlersKey] = Object.create(null);
}
if (!node[handlersKey][event]) {
node[handlersKey][event] = [];
}
node[handlersKey][event].push(callback);
}
function removeAllListeners(node)
{
if (!node[handlersKey]) {
return;
}
Object.keys(node[handlersKey]).forEach(function(event) {
removeListener(node, event);
});
delete node[handlersKey];
}
function removeListener(node, event)
{
var handlers = node[handlersKey];
var callbacks = handlers && handlers[event];
if (callbacks) {
callbacks.forEach(function(callback) {
node.removeEventListener(event, callback);
});
delete handlers[event]
}
}
listen(document.getElementById("target"), "mouseenter", function() {
console.log("Got mouseenter");
});
listen(document.getElementById("target"), "mouseleave", function() {
console.log("Got mouseleave");
});
listen(document.getElementById("target"), "click", function() {
console.log("Removing all listeners");
removeAllListeners(this);
});
<div id="target">Click me, I'll respond only once</div>

add to WebSocket.onmessage() like how jQuery adds to events?

I'm writing a single page ws++ site, and I'd like to keep my code grouped first by "page" (I think I need a new word since it never posts back) then by section then by concept etc.
I'd like to split up WebSocket.onmessage across my code much in the same way that $('#someElement') can constantly have an event like click(function(){}) added to it.
Can this be done with WebSocket.onmessage(function(){})? If so, how?
As some jQuery programmers happily know, an event can be initially set then added to in multiple places across the js. That's my favorite thing about js, the "put it anywhere as long as it's in order" ability. This makes code organization so much easier for me at least.
With WebSockets, really, the action client side for me so far is with the WebSocket.onmessage() handler since WebSocket.send() can be used anywhere and really just ports js data to the server.
onmessage() now owns my page, as whatever's in it initiates most major actions such as fading out the login screen to the first content screen upon a "login successful" type message.
According to my limited understanding of js, the onmessage() handler must be set all in one place. It's a pain to keep scrolling back/tabbing to another file to make a change to it after I've changed the js around it, far, far, away.
How can I add to the WebSocket.onmessage() handler in multiple places across the js?
To answer your last question;
how can I add to onmessage handler in multiple places across the js?
You can define your own personal (global) event handler in which you accept arbitrary number of handler functions. Here's an example:
window.bind: function(name, func, context) {
if (typeof this.eventHandlers[name] == "undefined") {
this.eventHandlers[name] = [func];
this.eventContexts[name] = [context];
}
else {
var found = false;
for (var index in this.eventHandlers[name]) {
if (this.eventHandlers[name][index] == func && this.eventContexts[name][index] == context) {
found = true;
break;
}
}
if (!found) {
this.eventHandlers[name].push(func);
this.eventContexts[name].push(context);
}
}
}
window.trigger: function(name, args) {
if (typeof this.eventHandlers[name] != "undefined") {
for (var index in this.eventHandlers[name]) {
var obj = this.eventContexts[name][index];
this.eventHandlers[name][index].apply(obj, [args]);
}
}
}
// === Usage ===
//First you will bind an event handler some where in your code (it could be anywhere since `.bind` method is global).
window.bind("on new email", function(params) { ... });
//Then you need to trigger "on new email" in `onmessage` once appropriate events happen.
WebSocket.onmessage(function(data) {
//Work on data and trigger based on that
window.trigger("on new email", { subject: data.subject, email: data.email });
})
This code is a part of an open source project I worked on before. It gives events names and let you set context for your handler (for methods instead of functions). Then you can call trigger in your onmessage handler of your socket. I hope this is what you are looking for.
You can create a wrapper which will handle WS events on itself. See this example CoffeeScript:
class WebSocketConnection
constructor: (#url) ->
#ws = new WebSocket(#url)
#ws.onmessage = #onMessage
#callbacks = []
addCallback: (callback) ->
#callbacks.push callback
onMessage: (event) =>
for callback in #callbacks
callback.call #, event
# and now use it
conn = new WebSocketConnection(url)
conn.addCallback (event) =>
console.log event
You can do it with addEventListener :
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
I've constructed a CoffeeScript class to solve this problem. It's similar to #Valent's but a bit more full-featured, so I figured I'd share it. It provides "on", "off", and "clear" methods for web socket events and also provides forwarding functions for "send" and "close" so that you pretty much don't have to touch the socket directly. If you do need access to the actual WebSocket object, you can get there by superWebSocket.ws.
edit: I added a getConnection static method to produce url-dependent singletons. This way there's only one connection per url and if you attempt to create a 2nd, it just gives you the existing one. It also protects against anyone calling the constructor directly.
edit: I communicate across the socket in JSON. I added some code that will run JSON.stringify on any non-string passed into send and also will attempt to run JSON.parse on any message received via a handler.
superSockets = {}
class SuperWebSocket
#getConnection: (url)->
superSockets[url] ?= new SuperWebSocket url
superSockets[url]
constructor: (url)->
if arguments.callee.caller != SuperWebSocket.getConnection
throw new Error "Calling the SuperWebSocket constructor directly is not allowed. Use SuperWebSocket.getConnection(url)"
#ws = new WebSocket url
events = ['open', 'close', 'message', 'error']
#handlers = {}
events.forEach (event)=>
#handlers[event] = []
#ws["on#{event}"] = (message)=>
if message?
try
message = JSON.parse message.data
catch error
for handler in #handlers[event]
handler message
null
on: (event, handler)=>
#handlers[event] ?= []
#handlers[event].push handler
this
off: (event, handler)=>
handlerIndex = #handlers[event].indexOf handler
if handlerIndex != -1
#handlers[event].splice handlerIndex, 1
this
clear: (event)=>
#handlers[event] = []
this
send: (message)=>
if typeof(message) != 'string'
message = JSON.stringify message
#ws.send message
close: => #ws.close()

Categories

Resources