Event emitter with conditions - javascript

I've created an event emitter class. It works properly. Let me tell you how:
This is the class:
class EventEmitter{
constructor() {
this.events = {};
}
on(eventName,callback) {
if(this.events[eventName]) {
this.events[eventName].push(callback);
} else {
this.events[eventName] = [callback];
}
}
trigger(eventName, ...rest) {
if(this.events[eventName]) {
this.events[eventName].forEach(cb => {
cb.apply(null,rest);
});
}
}
}
With this class i can listen to certain events. Like so :
const ee = new EventEmitter();
ee.on('change', (aa) => {
console.log(aa);
});
Then i can trigger it with the trigger method
ee.trigger('change','Argument');
Now i want to listen to events with certain conditions.
for example :
ee.on({'change': 'barack the boss'}, (aa) => {
console.log(aa);
});
The above bit of code should only execute when trigger looks like this:
//wont run
ee.trigger('change','barack is no boss');
//will run
ee.trigger('change','barack the boss');
I wonder how to do this. I am not asking you to write my code, I would like an example or a step in the right direction.
Thank you in advance.
Whole code:
class EventEmitter{
constructor() {
this.events = {};
}
on(eventName,callback) {
if(this.events[eventName]) {
this.events[eventName].push(callback);
} else {
this.events[eventName] = [callback];
}
}
trigger(eventName, ...rest) {
if(this.events[eventName]) {
this.events[eventName].forEach(cb => {
cb.apply(null,rest);
});
}
}
}
//events has been created and is currently an empty object
const ee = new EventEmitter();
//even has been created, this event has a function which will be executed when event is triggered
ee.on({'change': 'barack the boss'}, (aa) => {
console.log(aa);
});
//wont run
ee.trigger('change','barack is no boss');
//will run
ee.trigger('change','barack the boss');

What I would do is use Object.Keys on the .on() event register. This would allow you to iterate trough the object passed (which at the same time would give you the ability to register multiple events in a single call).
With Object.Keys you can iterate trough the argument object as if it were an array. Then you can register the the value of the key as a condition when triggering the event.

Related

Running a function before subscribing

I would like to add a form control as soon as the user has selected an option.
My select function:
selected(event: MatAutocompleteSelectedEvent): void {
this.setTechnologies = new Set();
this.setTechnologies.add(this.techInput.nativeElement.value);
}
Adding a new controller
this.primaryFormGroup.addControl('tech', new FormControl('', []));
this.primaryFormGroup.valueChanges.subscribe(inputFields => {
if (inputFields) {
inputFields.tech = Array.from(this.setTechnologies);
}
}
My problem is that the line inputFields.tech = Array.from(this.setTechnologies); will be executed, before the function selected() could be run. So in this case the value of inputFields.tech is ALWAYS empty.
How can I run the function first?
One approach is to add a slight delay to valueChanges. This ensures that selected() function is run before valueChanges.
this.primaryFormGroup.valueChanges.pipe(
delay(500)
).subscribe(inputFields => {
if (inputFields) {
inputFields.tech = Array.from(this.setTechnologies);
}
}

ES6 invoke class method by string value

I(m trying to use a generic method to parse dom element and attach event Listener to them.
So I add an html5 data attribute to specify the method that should be call when we trigger the event.
What I'm looking for is a way to call the method inside the class by her string value :
static addEvent(element, trigger, action)
{
element.on(trigger, action);
}
where element is the dom element where to attach eventListener, trigger is the type of listener and action is the method that we should call inside this class (here I want to call toggleFullscreen).
Is there a way to do that?
Edit : need to avoid eval solution, and I made a test with this[action] but it's not the excepted result.
Here is my code
dom element :
<div class="interface">
<span class="home_common-fullscreen action fa fa-desktop" data-action="toggleFullscreen" aria-hidden="true"></span>
</div>
javascript function :
class Interface {
constructor() {
this.domElements = jQuery(".interface");
this.attachAction();
}
attachAction()
{
let fullScreenButtonElement = this.domElements.find(".action");
if(fullScreenButtonElement !== undefined)
{
if(Array.isArray(fullScreenButtonElement))
{
jQuery(fullScreenButtonElement).each(function()
{
Interface.parseAction(this);
});
}
else
{
Interface.parseAction(fullScreenButtonElement);
}
}
}
static parseAction(element)
{
let action = element.attr("data-action");
if(action === undefined)
{
throw new InterfaceError("Trying to parse action element without defined data-action attribut");
}
let trigger = typeof element.attr("data-trigger") !== 'undefined' ? element.attr("data-trigger") : "click";
try
{
Interface.addEvent(element, trigger, action);
}
catch(err)
{
console.log(action + "not found in Interface")
}
}
static addEvent(element, trigger, action)
{
element.on(trigger, action);
}
toggleFullscreen()
{
alert("foo");
}
}
JSFiddle
class Interface {
// This method is a property of the class itself
static addEvent(element,trigger,action) {
element.on(trigger,Interface[action]);
}
// May need to be static if you don't plan on creating objects
static toggleFullscreen() {
console.log("It Works");
}
}
// Or the Event Handler could be in another object
class EventHandlers {
// It'd be accessed with EventHandlers["toggleFullscreen"];
static toggleFullscreen() {
}
}
// Or make it a global function
// Which would be accessed with window["toggleFullscreen"];
function toggleFullscreen() {
}
Do you need something like this:
class XClass {
f2() {
console.log("Message from f2");
}
f4() {
eval("this.f2()");
}
}
But I think static will not work for this approach.

Angular2 Custom Events

I'm trying to find out how to handle custom DOM events emitted by something outside of Angular, for example the following:
document.querySelector('my-custom-element').dispatchEvent(new Event('my.customEvent'));
So far I have tried to register a new EventManagerPlugin that supports everything starting with 'my.' but if I print out all events that come by all 'normal' event like 'click' and 'submit' are printed out; but none of my custom events.
html:
<my-custom-element (my.customEvent)="handleCustomEvent($event)"></my-custom-element>
ts:
supports(eventName: string):boolean {
var ret = false;
if (eventName.indexOf('my.') === 0) {
ret = true;
}
console.log('supports event?', eventName, ret);
return ret;
}
The console.log line only prints native events and ng*events but not my custom event :(
EDIT Fixed solution
I've moved the (my.customEvent) inside the component annd the log showed the custom event.
Binding an external event to the angular2 internal event while seperating the 2 is fixed by using a custom eventHandler in the EventManagerPlugin
Relevate code
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
let zone = this.manager.getZone();
// Entering back into angular to trigger changeDetection
var outsideHandler = (event: any) => {
zone.run(() => handler(event));
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
this.manager.addEventListener(element, 'external.' + eventName, outsideHandler);
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
Trigger the event via DOM:
document.querySelector('my-custom-element').dispatchEvent(new Event('external.my.customEvent'));
Now you can trigger an event from the DOM which is pushed into angular2 world and can the code is handled from within the component.
Try to extend the DomEventsPlugin, for example:
import {DomEventsPlugin} from 'angular2/platform/common_dom';
// Have to pull DOM from src because platform/common_dom returns DOM as null.
// I believe its a TS bug.
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {noop} from 'angular2/src/facade/lang';
#Injectable()
export class DOMOutsideEventPlugin extends DomEventsPlugin {
eventMap: Object = {
"clickOutside": "click",
"mousedownOutside": "mousedown",
"mouseupOutside": "mouseup",
"mousemoveOutside": "mousemove"
}
supports(eventName: string): boolean {
return this.eventMap.hasOwnProperty(eventName);
}
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var zone = this.manager.getZone();
var documentEvent = this.eventMap[eventName];
// Entering back into angular to trigger changeDetection
var outsideHandler = (event) => {
zone.run(() => handler(event))
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
DOM.onAndCancel(DOM.getGlobalEventTarget('document'), documentEvent,
(event) => {
let current = event.target;
// if the element/event is propagating from the element its bound to, don't handle it.
if (current.parentNode && current !== element) {
outsideHandler(event);
}
});
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var element = DOM.getGlobalEventTarget(target);
var zone = this.manager.getZone();
var outsideHandler = (event) => zone.run(() => handler(event));
if ((target === "document") || (target === "window" )) {
return noop;
}
return this.manager.getZone().runOutsideAngular(
() => DOM.onAndCancel(element, eventName, outsideHandler)
);
}
}
source: https://medium.com/#TheLarkInn/creating-custom-dom-events-in-angular2-f326d348dc8b#.so0jvssnz

how to trigger "online" event manually [duplicate]

I was wondering if anyone can help me understand how exactly to create different Custom event listeners.
I don't have a specific case of an event but I want to learn just in general how it is done, so I can apply it where it is needed.
What I was looking to do, just incase some folks might need to know, was:
var position = 0;
for(var i = 0; i < 10; i++)
{
position++;
if((position + 1) % 4 == 0)
{
// do some functions
}
}
var evt = document.createEvent("Event");
evt.initEvent("myEvent",true,true);
// custom param
evt.foo = "bar";
//register
document.addEventListener("myEvent",myEventHandler,false);
//invoke
document.dispatchEvent(evt);
Here is the way to do it more locally, pinpointing listeners and publishers:
http://www.kaizou.org/2010/03/generating-custom-javascript-events/
Implementing custom events is not hard. You can implement it in many ways. Lately I'm doing it like this:
/***************************************************************
*
* Observable
*
***************************************************************/
var Observable;
(Observable = function() {
}).prototype = {
listen: function(type, method, scope, context) {
var listeners, handlers;
if (!(listeners = this.listeners)) {
listeners = this.listeners = {};
}
if (!(handlers = listeners[type])){
handlers = listeners[type] = [];
}
scope = (scope ? scope : window);
handlers.push({
method: method,
scope: scope,
context: (context ? context : scope)
});
},
fireEvent: function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (typeof(context)!=="undefined" && context !== handler.context) continue;
if (handler.method.call(
handler.scope, this, type, data
)===false) {
return false;
}
}
return true;
}
};
The Observable object can be reused and applied by whatever constructor needs it simply by mixng the prototype of Observable with the protoype of that constructor.
To start listening, you have to register yourself to the observable object, like so:
var obs = new Observable();
obs.listen("myEvent", function(observable, eventType, data){
//handle myEvent
});
Or if your listener is a method of an object, like so:
obs.listen("myEvent", listener.handler, listener);
Where listener is an instance of an object, which implements the method "handler".
The Observable object can now call its fireEvent method whenever something happens that it wants to communicate to its listeners:
this.fireEvent("myEvent", data);
Where data is some data that the listeners my find interesting. Whatever you put in there is up to you - you know best what your custom event is made up of.
The fireEvent method simply goes through all the listeners that were registered for "myEvent", and calls the registered function. If the function returns false, then that is taken to mean that the event is canceled, and the observable will not call the other listeners. As a result the entire fireEvent method will return fasle too so the observable knows that whatever action it was notifying its listeners of should now be rolled back.
Perhaps this solution doesn't suit everybody, but I;ve had much benefit from this relatively simple piece of code.
From here:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
// create the event
const event = new Event('build');
// elem is any element
elem.dispatchEvent(event);
// later on.. binding to that event
// we'll bind to the document for the event delegation style.
document.addEventListener('build', function(e){
// e.target matches the elem from above
}, false);
Here is a really simple (TypeScript/Babelish) implementation:
const simpleEvent = <T extends Function>(context = null) => {
let cbs: T[] = [];
return {
addListener: (cb: T) => { cbs.push(cb); },
removeListener: (cb: T) => { let i = cbs.indexOf(cb); cbs.splice(i, Math.max(i, 0)); },
trigger: (<T> (((...args) => cbs.forEach(cb => cb.apply(context, args))) as any))
};
};
You use it like this:
let onMyEvent = simpleEvent();
let listener = (test) => { console.log("triggered", test); };
onMyEvent.addListener(listener);
onMyEvent.trigger("hello");
onMyEvent.removeListener(listener);
Or in classes like this
class Example {
public onMyEvent = simpleEvent(this);
}
If you want plain JavaScript you can transpile it using TypeScript playground.

Removing event listener which was added with bind

In JavaScript, what is the best way to remove a function added as an event listener using bind()?
Example
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.addEventListener("click", this.clickListener.bind(this));
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", ___________);
};
})();
The only way I can think of is to keep track of every listener added with bind.
Above example with this method:
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.clickListenerBind = this.clickListener.bind(this);
this.myButton.addEventListener("click", this.clickListenerBind);
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", this.clickListenerBind);
};
})();
Are there any better ways to do this?
Although what #machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:
A new function reference is created after .bind() is called.
See Does bind() change the function reference? | How to set permanently?
So, to add or remove it, assign the reference to a variable:
var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);
This works as expected for me.
For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your component:
class App extends React.Component {
constructor(props){
super(props);
// it's a trick! needed in order to overcome the remove event listener
this.onChange = this.onChange.bind(this);
}
// then as regular...
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange () {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).
(As a side note, addEventListener doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)
jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));
We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener on the EventTarget prototype.
This will add a new function on objects element.removeAllEventListers("click")
(original post: Remove Click handler from fabric dialog overlay)
<script>
(function () {
"use strict";
var f = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.f = f;
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
this._eventHandlers[type].push([fn, capture]);
this.f(type, fn, capture);
}
EventTarget.prototype.removeAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
if (type in this._eventHandlers) {
var eventHandlers = this._eventHandlers[type];
for (var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
this.removeEventListener(type, handler[0], handler[1]);
}
}
}
EventTarget.prototype.getAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
return this._eventHandlers[type];
}
})();
</script>
Here is the solution:
var o = {
list: [1, 2, 3, 4],
add: function () {
var b = document.getElementsByTagName('body')[0];
b.addEventListener('click', this._onClick());
},
remove: function () {
var b = document.getElementsByTagName('body')[0];
b.removeEventListener('click', this._onClick());
},
_onClick: function () {
this.clickFn = this.clickFn || this._showLog.bind(this);
return this.clickFn;
},
_showLog: function (e) {
console.log('click', this.list, e);
}
};
// Example to test the solution
o.add();
setTimeout(function () {
console.log('setTimeout');
o.remove();
}, 5000);
As others have said, bind creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.
For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:
class MyClass {
activate() {
window.addEventListener('click', this.onClick);
}
deactivate() {
window.removeEventListener('click', this.onClick);
}
get onClick() {
const func = (event) => {
console.log('click', event, this);
};
Object.defineProperty(this, 'onClick', {value: func});
return func;
}
}
If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this) instead of const func = (event) => {...}.
Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.
If you want to use 'onclick', as suggested above, you could try this:
(function(){
var singleton = {};
singleton = new function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.onclick = function() {
singleton.clickListener();
};
}
singleton.clickListener = function() {
console.log(this); // I also know who I am
};
// public function
singleton.disableButton = function() {
this.myButton.onclick = "";
};
})();
I hope it helps.
can use about ES7:
class App extends React.Component {
constructor(props){
super(props);
}
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange = () => {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.
MDN :: EventTarget.addEventListener - The value of "this" within the handler
It gives a great alternative to the handleEvent function.
This is an example with and without bind:
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:

Categories