AngularJS scope variable can't be set inside of a timeout [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 3 years ago.
I have a jsfiddle for this issue.
https://jsfiddle.net/uvtw5kp1/4/
$scope.Dropdown = {
open: false,
searchValue: "",
timer: null,
hideIt: function() {
this.timer = $timeout(function() {
alert("timeout happened the value will not change");
this.open = false;
}, 50);
},
hideItNotimer: function() {
this.open = false;
},
showIt: function() {
$timeout.cancel(this.timer);
this.open = true;
}
};
When I call Dropdown.hideItNotimer() on ng-mouseout it has no problem, but when I call Dropdown.hideIt() the variable is not set. I have added an alert to ensure the timer is working, and I've tried doing a scope.apply after. What does work is having a scope level function called within the timer:
like this:
$scope.setDropdownHidden = function(){
$scope.Dropdown.open = false;
}
and call that from within the timeout it works, but I want to avoid this if I can.
What am I missing?

In your timeout function, this does not refer to the Dropdown object, it instead probably refers to the window object. In javascript this always refers to whichever object the function was invoked with, not the object in which it was defined. When $timeout invokes your callback function it will do so with something other than your Dropdown object since it doesn't know about that object.
Related: Javascript closures and this
You need to either capture the value of this in the parent function as a closure variable or bind your callback function to the Dropdown object using angular.bind

Timeout has its own scope since it is a closure, so the $scope.open = false is not updating your controller $scope.open variable, you should avoid using timeout for to update scope variables. You should bind the global scope with using bind as -
hideIt: function() {
this.timer = $timeout(function() {
this.open = false;
}.bind(this), 50);
}

Related

Strange Function Behavior with Prototype and Event Listeners - JavaScript [duplicate]

This question already has answers here:
The value of "this" within the handler using addEventListener
(10 answers)
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
Original Modal
I want to use a universal app object. To this empty object I will add functions as needed. The issue is that some functions will need to all others within the app object.
So, my question is: how do I construct a large object without having to define all functions inside the object at the time of creation? I would like to split up the chunks of code to not have one astronomical long .js file.
There is a simple example of my original code:
var app = {
tow: function () {
return true;
},
one: function () {
return this.tow();
}
};
// app.one() => returns true
Updated Modal
Here is something I found interesting. I was playing around with the prototype modal and discovered something strange. When I use this model I can add functions that can call other added functions. But, when I create an event listener it is unable to run the code. Can anyone explain why this is?
Modified code with unexpected result:
function modal () {}
modal.prototype.one = function () {
return this.two();
};
modal.prototype.two = function () {
return "cool";
};
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", this.one);
}
var app = new modal();
app.init();
// app.one() => returns true
// event listener => returns "TypeError: this.two is not a function"
JSBIN: https://jsbin.com/vureruziza/edit?js,console,output
this.one called as you done refers to addEventListener function, not to your object. This will solve the issue
modal.prototype.init = function () {
var self = this;
document.getElementById('go')
.addEventListener("click", function(){
self.one()
});
}
bind the click function with this cause the function will need the this context, not the window context. Then call your this.one function in de click handler.
function modal () {}
modal.prototype.one = function () {
return this.two();
};
modal.prototype.two = function () {
return "cool";
};
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", function(e){
console.log(this.one())
}.bind(this));
/*
The following wil also be called but your return value
of your this.one function won't be captured. But your code will run.
.addEventListener("click", this.one.bind(this));
Try replacing it with the above and put a debugger statement in this.one
and see that the code will actualy be ran, just not captured to output.
*/
}
var app = new modal();
app.init();
// app.one() => returns true
// event listener => returns "TypeError: this.two is not a function"
<div id="go">go</div>
Use ES6 fat arrow function. Update modal.prototype.init as below -
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", () => this.one());
}
Edit - If you wanted to debug the issue, you could just console.log the this value in function one like so -
modal.prototype.one = function () {
console.log(this);
return this.two();
};
You will most likely see the window object. You will certainly not see the modal object.

Javascript - referring to "this" in function run upon click [duplicate]

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 7 years ago.
I have a method of an object that is called upon click, and the this unfortunately refers to the window object instead of the object that I want it to be referring to.
I've read a several posts that mention this issue, but I have not found/understood any alternatives for my situation.
Any assistance in which direction to go would be appreciated
JSFIDDLE
var IceCream = function(flavor) {
this.tub = 100;
this.flavor = flavor;
};
IceCream.prototype = {
scoop: function() {
this.updateInventory();
alert("scooping");
},
updateInventory: function() {
this.tub--;
alert(this.tub);
}
};
var vanilla = new IceCream("vanilla");
vanilla.scoop();
$('button').click(vanilla.scoop);
$('button').click(function(){
vanilla.scoop();
});
Change the last line to this should make it work. The $.fn.click function takes a callback, with event as the first argument, and binds the element as this, not window.
------------Edit------------
To make this a little bit cleaner, you can define a function to return a click function. Define your class like so:
IceCream.prototype = {
scoop: function() {
this.updateInventory();
alert("scooping");
},
updateInventory: function() {
this.tub--;
alert(this.tub);
},
makeClickFn: function(){
var self = this;
return function(event){
self.scoop();
};
}
};
And when you need to bind the click function:
$('button').click(vanilla.makeClickFn());
jsfiddle: https://jsfiddle.net/L7e71sLL/
$('button').click(vanilla.scoop.bind(vanilla));
Bind, creates a function whose this variable is bound to the given object. In this case vanilla
Inside $('button').click() the scope will be of the button. So you need to bind the vanilla scope to the onClick function

Passing Parameters into a Callback Function

I have a function that listens for a click on the screen and fires a callback. It is part of a Helper object (which is why is preceded by the term Helper in my sample code. That is irrelevant however.
var Helper = {
bodyClickListener: function(fn) {
var window = document.getElementsByTagName('body')[0];
window.click();
CORE.dom.on(window, 'click', function(event) {
CORE.dom.off(window, 'click');
fn(event);
});
}
}
I need to be able to pass a function into this function with a parameter that has been previously set.
function someFunction() {
var popup = document.getElementById('tagResultsPopup');
Helper.bodyClickListener(function(popup) {
return function(event) {
event.stopPropagation();
removePopup(popup);
};
}(document.getElementById('tagResultsPopup')));
function removePopup(element) {
if(element) {
element.parentNode.removeChild(element);
}
};
}
The code above works, but you'll notice that I have to set the popup variable inside of the callback function. It has already been set above. How do I pass a reference to the earlier variable into the callback function.
If I understand your question correctly, you don't need to do much. You can just use the popup variable defined outside.
var popup = document.getElementById('tagResultsPopup');
Helper.bodyClickListener(function(event) {
event.stopPropagation();
//Don't set it
//var popup = document.getElementById('tagResultsPopup');
removePopup(popup);//popup will refer to the correct variable
});
The function that you are passing to bodyClickListener is a closure. You can simply reference 'popup' inside that function without any problem. You don't have to create a new variable.
The answer was to use closure in this way:
Helper.bodyClickListener(function(popup) {
return function(event) {
event.stopPropagation();
removePopup(popup);
};
}(document.getElementById('tagResultsPopup')));
That way the callback function has access to the variable I pass into the parameter function. So here, the return is actually the function I am passing as the callback.

JS Object, how to access objects from within myself

So, i see no reason why this isn't working but i am at a wall and frustrated. Why can't i call this.myself from within the wyr.message.close function? Maybe my understanding of this is scewed but i was sure this is referring to the object itself, not the function.
this.myself is undefined
Code:
wyr.message = {
myself: $('.message'),
init: function() {
if(this.myself.is(':visible')){
setTimeout(this.close, 5000);
}
},
close: function(){
this.myself.fadeOut(1200,function(){
this.myself.remove();
});
}
}
wyr.message = {
myself: $('.message'),
init: function() {
var self = this;
if(this.myself.is(':visible')){
setTimeout(function(){
self.close();
}, 5000);
}
},
close: function(){
this.myself.fadeOut(1200,function(){
$(this).remove();
});
}
}
The issue is context. Within the callback function passed to fadeOut, this is bound to the element being worked on by jQuery, not to the wyr.message object.
EDIT:
There's also an issue with the init method. SetTimeout will bind the value of this to the global (window) object - so we save a reference to the this we want and use that to invoke the close method.
You could also look into Function.prototype.bind, but it's not supported in older browsers.
First, javascript object literal property expressions are evaluated at the time you create the object instance. myself will be whatever that selector grabs at that time, which is probably nothing. The myself property needs to be a function if you want it to return the value of $('.message') at the time of invocation. As a consequence you'll need to change all uses to function calls as well.
Second, during the execution of the setTimeout callback, this is bound to the window object, so you need to qualify it appropriately:
wyr.message = {
myself: function() { return $('.message'); },
init: function() {
if(this.myself().is(':visible')){
setTimeout(this.close, 5000);
}
},
close: function(){
message.myself().fadeOut(1200,function(){
$(this).remove();
});
}
};
(Note, this will fade out and remove everything matching the selector when the timeout fires.)

How to access attribute in jQuery callback func?

var some_name =
{
moving:false,
show : function ()
{
this.moving = true;
$('element').slideDown(5000, function ()
{
this.moving = false; //How to access to attribute "moving" of class some_name?
});
},
}
Question in code.
You can bind the callback function to the current context:
$('element').slideDown(5000, $.proxy(function() {
this.moving = false;
}), this); // "this" inside of the function will be this "this"
See jQuery.proxy
Alternatively you could do this:
this is the current context, it's value depends on how the function is called. You can assign this to a variable outside of the function, and use this variable instead:
var that = this;
$('element').slideDown(5000, function() {
that.moving = false; //use that instead of this here
});
Use moving instead of this.moving (in both occurences)
Variables are bound to the context when they are used, so even inside your event callback you can access the variables above.
In the event callbacks, this refers to event.target, or the element that captured the event.
You can take the advantage of closures in javascript and access the moving attribute like this:
show : function ()
{
var moving = true;
$('element').slideDown(5000, function ()
{
moving = false;
});
},
Note, though, that this moving will be different of the first moving that lives in some_name

Categories