Using TypeScript (JavaScript and Angular):
I want lodash's throttle decorator to limit an API call while the user is navigating around the page, but still fire before they unload (leave) the site.
In the typescript constructor I have window.addEventListener('beforeunload', () => this.onUnload());
with the onUnload() function being declared as
onUnload() {
this.thisIsTheThrottledFunction.flush;
}
but I am getting the error response "Property 'flush' does not exist on type '() => Promise'."
The function whose .flush method I am trying to access is the declared throttled version of the other function. The function is successfully throttled, so I am confident that part of the code works. What is the best way to access the .flush method?
You should be able to debug this by verifying what the value of this is. Seems to me like you just need to bind the object's this value to the onUnload function (or you can pass it in). For instance, you could put this in your constructor: this.onUnload = this.onUnload.bind(this). There's a sugar for this syntax, where you define it in your class using onUnload = () => { ... }. Both of those methods attach the method to the instance instead of just having it as part of the prototype. Or, you could pass the bound function directly to your event listener:
window.addEventListener('beforeunload', this.onUnload.bind(this));
Related
So lets rewrite this to fit the correct answer I now remember asking however long ago.
Jfriend00's answer in the comments is correct.
Given the use case of an emitted event from an object that is derived from eventemitter
I wanted to know how to get a reference to the object itself.
So eg.
var myObj = new EmitterDescendent(); // some descendent of an emitter class
myObj.name="123"; // some form of object tag.
myObj.on('eventofsomesort', ()=>
{
var ref = (some manner of grabbing a reference to the object);
console.log (ref.name);
});
// expected output: 123
Apparrently lambda screws up the 'this' keyword when the event handler is being called, I do not no why as using the function keyword should still be seen as an anonymous function.
Jfriend00's answer was what I was looking for.
Thanks very much.
This should be obvious. You already know which object emitted the event, because you registered an event handler on it. You should have tested your cod and would have found that Object doesn't have an on function:
> Object.on('event', console.log)
Uncaught TypeError: Object.on is not a function
Instead, in node.js you need to extend EventEmitter, and then register listeners on the instance of that class. So you will know inside the handler who emitted the event.
EventEmitter = require('events');
e = new EventEmitter();
e.on('event', () => {
console.log(e, 'emitted event');
});
e.emit('event');
I'm going to ignore your example code using Object because there is no .on() method on Object. That's a method that exists on the EventEmitter class. Perhaps you didn't mean that to indicate the actual Object class in Javascript.
Assuming you had some code that was actually using an EventEmitter, a subclass of EventEmitter or some code that implements that interface, then if you declare your listener with a regular function, NOT with an arrow function, then the value of this will be the object that emitted the event.
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on("hi", function(data) {
console.log(this === myEmitter); // logs true
});
myEmitter.emit("hi", "hello");
Note, that in addition to using the value of this, if you're using an inline function declaration for the listener function, you also have direct access to the variable that you registered the event listener on. In this case, it's the myEmitter variable. If your listener is an external function, then that option is not available to you and you can use the value of this. I remind you that you must register your listener as a regular function, not an arrow function because the arrow function overrides the value of this.
Here's a doc reference to a discussion about using this in a listener.
I guess that your Object has extended from EventEmitter somehow. Nodejs EventEmitter events are not the same as HTML5 events. There's no target or currentTarget properties as the callback arguments are just the "raw" arguments passed in the emit() call.
You already have the object reference, and there are better ways of doing it, but if you really want to receive the object in the function callback, another way fo doing it, apart from using scopes and closures like the other answer, is to create a function with a binded argument, like so (by the way, I personally prefer the scopes/closures one instead of the binding one):
Object.name="123";
Object.on('event', (function(ref) {
console.log(ref.name);
}).bind(this, Object));
Object.emit('event');
You can even have the same function and bind it each time for each object, like so:
function funccallback(ref) {
console.log(ref.name);
}
var object1 = {}; // Extend from "EventEmitter" somehow or it will not work
object1.name="123";
object1.on('event', funccallback.bind(this, object1));
object1.emit('event');
var object2 = {}; // Extend from "EventEmitter" somehow or it will not work
object2.name="124";
object2.on('event', funccallback.bind(this, object2));
object2.emit('event');
Im trying to build an app with Cytoscape.js and Angular2+.
The Core part take place in a single service (cy.service).
I add all Eventlisteners in a single function and call it after the cy init.
eg:
initEventlisteners(){
cy.on('tap', this.handler);
cy.on(...);
...
}
handler(event: any){
console.log('something');
}
If I wrap console.log in a helper-function like that:
helper(){
console.log('something');
)
And use it in the handler-function
handler(event:any){
this.helper();
}
It calls: helper() is not a function.
Thats a big problem that a cant use other functions inside the handler.
Any ideas how to solve that problem ?
That's because with binding like this cy.on('tap', this.handler); you are loosing scope of variable. You should use it in this way cy.on('tap', (event) => { this.handler(event) }); (if it's passing any event object) or cy.on('tap', this.handler.bind(this));.
The frist approach with () => { } is called arrow function, which preserve current scope of variables. It's a new feature of ES6 specification. However you don't have to be afraid, angular compiler will convert it to backward compatible to ES5.
The second approach .bind(this) just simply binding this method to current scope where it's was called.
I have a class that I use to load external resources via an XMLHttpRequest (this is for WebGL) so I'm loading models, shaders etc. My plan was to put a loading display up whilst it did all these requests and then when it's finally complete I want it to run a callback function from the original function that created it. However, I'm getting strange results when I try to run that call back (such as it has no access of any of the objects within the class that did the loading).
I can get around this problem by passing "this" into the loading class and then doing
self = this;
promise(self.callback());
but I'd much rather specify the function that I want it to callback to after its done the loading. Does anyone know if this can be done? My code looks like this:
Main Class
this.loadingClass = new LoadingClass(this.LoadingComplete, resources);
Main.prototype.LoadingComplete = function()
{
// Returns undefined if i specify the callback function instead of just "this"
console.log(this.loadingClass.anyOfTheMembersOfThisClass);
}
Loading Class
LoadingClass = function(callback, resources) {
..
Promise.all(resources).then(function(loadedResources)
{
..
callback();
});
}
When you pass the function object as
(this.LoadingComplete, resources)
the object to which it was bound, will not be passed. So, only the function object LoadingComplete is passed to LoadingClass and when it is invoked as
callback()
the this value will be undefined (in strict mode).
To fix this,
you need to bind the this object, like this
new LoadingClass(this.LoadingComplete.bind(this), resources)
if your environment supports ES2015's Arrow functions
new LoadingClass(() => this.LoadingComplete(), resources);
In both these cases, when the LoadingComplete is invoked from LoadingClass, the this will be retained.
You are detouching callback (read about "this") function from the root object, so of course it loses context. Specify bindingContext explicitly with Function.prototype.bind method:
this.loadingClass = new LoadingClass(this.LoadingComplete.bind(this), resources);
So I have most of my functions and variables organized into small object-based modules, like so:
module1: {
someVariable: false,
someFunction: function(e) {
do some stuff using someVariable
},
someFunction2: function(e) {
do some other stuff
}
}
And I call these functions as callbacks during various events, like so:
$(function() {
$('.thing').on('mouseenter', module1.someFunction);
}
Now, from within someFunction, I would expect the 'this' keyword to refer to the object in which the function is contained. Instead, it refers to the DOM element that triggered the event that fires the function. Is there anyway I can get access to, say the someVariable variable in the function's containing object other than writing module1.someVariable?
The shortest answer is to try this:
$(function() {
$('.thing').on('mouseenter', function(e) {
module1.someFunction(e);
});
}
The 'this' value is only set to the object the method is attached to if the method is invoked directly on the object:
module1.someFunction(); // direct invocation, 'this' will be set properly
var tempFunc = module1.someFunction;
tempFunc(); // the function was removed first. 'this' will NOT be set
In your case, you are pealing the method off of the object and handing it to an event handler. The event handler doesn't know about the object and doesn't perform a direct invocation.
In fact, the event handler explicitly overrides the context because that is how the jQuery API is defined. You have to explicitly override it back if you want the behavior you're talking about.
Using a library like underscore.js you could also bind the function as you pass it off to the event handler.
$(function() {
$('.thing').on('mouseenter', _.bind(module1.someFunction, module1));
}
I believe that Object.bind is supposed to be natively supported in the future with no libraries, but you can't yet rely on the older browsers to support it.
I have wrote some cross browser code for adding event listeners, then chrome started being funky, anyone know why this is happening?
Add Event listener code:
function addEventListener(Elm,Type,Func)
{
if(Elm.attachEvent)
Elm.attachEvent((Type.substr(0,2) == 'on' ? Type : 'on'+Type),Func);
else
Elm.addEventListener(Type,Func);
}
Code calling the method:
addEventListener(window,'load',SetSize);
addEventListener(window,'resize',SetSize);
Error:
Uncaught TypeError: Object load has no method 'addEventListener'
You can very clearly see that I have passed the arguments in the correct order yet they are not interpreted in said order..
You have overwritten window.addEventListener.
The native signature is: event_name, callback but yours is: object, event_name, callback.
Change the name of your function addEventListener or namespace it, like my_framework.addEventListener
You have redefined window.addEventListener. Anything you declare in the global namespace basically belongs to window, so:
function addEventListener(...) {
}
is the same as:
window.addEventListener = function(...) {
}
The argument signature for the native addEventListener is eventName, listener, but you have Elm, Type, Func.
Then inside your function body, you are doing Elm.addEventListener and passing it 'load' and SetSize. In that call, it calls your function again (because Elm is window) and this time, it attempts to call addEventListener on the string 'load', which won't work because a string doesn't have that method.
Change the name of your function, or namespace it, and it should work.
I would say the window object has not .attachEvent or .addEventListener.
This may caused because your function is named addEventListener and has overwritten the window.addEventListener()