Angular: How to access parameters outside of $timeout callback? - javascript

I have a timeout loop like this:
var somedata = {
autoRefreshInterval: 300,
autoRefreshInSec: 300,
myTimeout: null,
doRefresh: _doRefresh,
onTimeout: function () {
this.autoRefreshInSec--;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
this.doRefresh();
}
this.myTimeout = $timeout(this.onTimeout, 1000);
},
startTimer: function () {
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout, 1000);
},
stopTimer: function () {
$timeout.cancel(this.myTimeout);
},
}
It appears that the "this" doesn't work inside of onTimeout callback function, while it works fine for startTimer and stopTimer. How to fix it?
UPDATE:
Since this is lost inside the onTimeout based on one of the answers below, I tried to pass it into like this:
onTimeout: function (self) {
self.autoRefreshInSec--;
if (self.autoRefreshInSec <= 0) {
self.autoRefreshInSec = self.autoRefreshInterval; // Set it here so that it does't start refresh right away. It will be reset when refresh is done.
self.doRefresh();
}
self.myTimeout = $timeout(self.onTimeout(self), 1000);
},
startTimer: function () {
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout(this), 1000);
},
Strangely, when I debug through the code, it seems working. However, once I removed the break points, self.doRefresh() is fired continuously. Why?
UPDATE 2:
Okay, I created a JSFiddle at http://jsfiddle.net/qY86q/1 to illustrate the problem.

Function.prototype.bind()
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Solution for your case
angular.module('myApp', [])
.service('timerService', function($timeout) {
var _timer = {
autoRefreshInterval: 300,
autoRefreshInSec: 300,
myTimeout: null,
onTimeout: function() {
this.autoRefreshInSec -= 1;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
console.log('refreshing');
}
console.log('time: ', this.autoRefreshInSec);
this.myTimeout = $timeout(this.onTimeout.bind(this), 1000);
},
startTimer: function() {
if (this.myTimeout) {
this.stopTimer(this.myTimeout)
}
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout.bind(this), 1000);
},
stopTimer: $timeout.cancel // see note(1)
};
var context = {
timer: _timer
};
return context;
}).controller('PrefsCtrl', function PrefsCtrl($scope, timerService) {
$scope.timer = timerService.timer;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="PrefsCtrl">
<button ng-click="timer.startTimer()">Click to Start or Reset Timer</button>
<div>{{timer.autoRefreshInSec}}</div>
</div>
</div>
note(1), this is shorcut for
stopTimer: function(timer) {
$timeout.cancel(timer)
}

This is a javascript binding issue.
Try:
var somedata; // now this can be referenced in the angular.bind call
somedata = {
onTimeout: angular.bind(somedata, function () {
this.autoRefreshInSec--;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
this.doRefresh();
}
this.myTimeout = $timeout(this.onTimeout, 1000);
})
}
which will ensure the this at the time that function is called is "bound" to somedata inside the callback.

Related

fireCustomEvent is undefined

I'm getting error saying Uncaught TypeError: Cannot read properties of undefined (reading 'fireCustomEvent'). Please help how to resolve this as I'm making use of Oracle JET to write this javascript code
define([], () => {
'use strict';
//var eventHelper;
var idleTime = 0;
class PageModule {
constructor(context) {
this.eventHelper = context.getEventHelper();
// Increment the idle time counter every minute.
this.idleInterval = setInterval(this.timerIncrement, 5000); // 10 second
}
timerIncrement() {
console.log(idleTime);
idleTime = idleTime + 1;
if (idleTime > 0) {
console.log(this.eventHelper);
this.eventHelper.fireCustomEvent('openDialog', {});
}
}
}
// Zero the idle timer on mouse movement.
$(this).mousemove(function (e) { idleTime = 0; });
$(this).keypress(function (e) { idleTime = 0; });
return PageModule;
});
This issue may be related to Referencing "this" inside setInterval/setTimeout.
With the current code, after transpile, the PageModule will be
var PageModule = /** #class */ (function () {
function PageModule(context) {
//.....
this.idleInterval = setInterval(this.timerIncrement, 5000); // 10 second
}
PageModule.prototype.timerIncrement = function () {
if (idleTime > 0) {
console.log(this.eventHelper);
this.eventHelper.fireCustomEvent('openDialog', {});
}
};
return PageModule;
}());
this is not referring to PageModule but Window object of browser inside the timerIncrement function and because of that the eventHelper is undefined.
Solution 1: Use an arrow function.
const timerIncrement = () => {
//....
if (idleTime > 0) {
console.log(this.eventHelper);
this.eventHelper.fireCustomEvent('openDialog', {});
}
}
Solution 2:
Pass this to timerIncrement as given below and use as self inside the timerIncrement function.
constructor(context) {
this.idleInterval = setInterval(this.timerIncrement, 5000, this);
}
timerIncrement(self) {
//....
if (idleTime > 0) {
console.log(self.eventHelper);
self.eventHelper.fireCustomEvent('openDialog', {});
}
}

How to you make a Javascript object's method call setInterval with an object method as an argument

I am trying to create a generic countdown timer object in Javascript
I have a method (decrementTimer) that reduces the timeLeft on counter by a fixed amount and another method which uses setInterval to call decrementTimer method.
The problem is that the code only runs once not every second. It looks like setInterval isn't putting the decrementTimer function on the queue inside my object.
I have tried making javascript do an Eval by putting the keyword "window" in front of the setIntervalfunction call but this doesn't work.
I can't use a Javascript class because I can't assume that all browsers support ECMAScript 6. I am using IE11.
I have also found solutions that work when you are doing this in a function but no examples of how to make this work in an object.
I would appreciate some help.
<script>
var myTimer = {
timeLeft: 10,
timerJobNumber: null,
decrementTimer: function(){
alert("decrementTimer called. timeLeft = " + this.timeLeft);
this.timeLeft = this.timeLeft - 1;
if(this.timeLeft<0){
alert("done");
}
},
startTimer: function(){
alert("startTimer called");
this.timerJobNumber = window.setInterval(this.decrementTimer(),10);
alert("Interval Job Number = " + this.timerJobNumber);
},
stopTimer: function(){
clearInterval(this.timerJobNumber);
alert(this.timerJobNumber);
alert("job terminated");
},
resetTimer: function(initialTime){
this.TimeLeft = initialTime;
alert("intitialTime="+intitialTime);
},
getTimeLeft: function(){
return this.timeLeft;
}
};
console.log(myTimer.getTimeLeft());
console.log(myTimer.startTimer() );
console.log(myTimer.getTimeLeft());
</script>
I didn't really check all of your code, but this seems to do what you want :
var myTimer = {
timeLeft: 10,
timerJobNumber: null,
decrementTimer: function(){
console.log("decrementTimer called. timeLeft = " + this.timeLeft);
this.timeLeft = this.timeLeft - 1;
if(this.timeLeft<0){
this.stopTimer();
}
},
startTimer: function(){
this.timerJobNumber = window.setInterval(this.decrementTimer.bind(this),1000);
console.log("Interval Job Number = " + this.timerJobNumber);
},
stopTimer: function(){
clearInterval(this.timerJobNumber);
alert(this.timerJobNumber);
},
resetTimer: function(initialTime){
this.TimeLeft = initialTime;
alert("intitialTime="+intitialTime);
},
getTimeLeft: function(){
return this.timeLeft;
}
};
Note that you can easily transform this into a class like function :
var MyTimer = function() {
this.timeLeft = 10;
this.timerJobNumber = null;
};
MyTimer.prototype.decrementTimer = function() {
console.log("decrementTimer called. timeLeft = " + this.timeLeft);
this.timeLeft = this.timeLeft - 1;
if(!this.timeLeft > 0)
this.stopTimer();
};
MyTimer.prototype.startTimer = function() {
this.timerJobNumber = window.setInterval(this.decrementTimer.bind(this),1000);
console.log("Interval Job Number = " + this.timerJobNumber);
};
MyTimer.prototype.stopTimer = function() {
clearInterval(this.timerJobNumber);
alert(this.timerJobNumber);
};
MyTimer.prototype.resetTimer = function(initialTime) {
this.timeLeft = initialTime;
alert("intitialTime="+intitialTime);
};
MyTimer.prototype.getTimeLeft = function() {
return this.timeLeft;
};
//...
var m = new MyTimer();
m.startTimer();
I think you don't bind your decrementTimer() inside the setInterval() function.
startTimer: function(){
alert("startTimer called");
this.timerJobNumber = window.setInterval(this.decrementTimer.bind(this),10);
alert("Interval Job Number = " + this.timerJobNumber);
}
if you are not familiar this topic, then follow this link. I think it will help you.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

Make a jquery function wait till it's previous call has been resolved

So, I've searched for this high and low and maybe I'm just having trouble understanding jQuery's deferred function or I'm completely on the wrong track. So any help would be appreciated folks!
I basically have a custom jQuery function messager that displays a message with a fadeOut and fadeIn.
(function ( $ ) {
$.fn.messager = function(message, effect, speed) {
$(this).fadeOut(speed).delay(speed).text(message).fadeIn(speed);
return this;
};
}( jQuery ));
So, I have a div called $elem and when $elem.messager gets called multiple times (with different messages), I would like the messager function to wait till its last call has finished. As in the last FadeIn has finished. Because currently what's happening is that the second call of the function is overwriting the animation effect of the first call of the function.
Any ideas?
jQuery Deferred Way
jQuery Deferred object (roughly compromising CommonJS Promises API) can help us managing queued operations. Here is my implementation of queuing messages. You can pass through multiple messages as an array in one call, or synchronize different message boards easily because #messager() returns jQuery object itself but also wrapped as a promise object which will be resolved just when message(s) being displayed.
(function ($) {
function awaits($el) {
var awaits = $el.data('_awaits');
awaits || $el.data('_awaits', awaits = []);
return awaits;
}
function resolveNext(curr /*, ignored */) {
var head = awaits(this).shift();
if (head === curr) {
resolveNext.call(this, 'not await');
} else {
head && head.resolve();
}
}
function display(message, speed) {
var $self = this, await = $.Deferred(), willDone = $.Deferred();
awaits($self).push(await) > 1 || await.resolve();
await.done(function() {
function reveal() {
$self.text(message).fadeIn(speed, function() {
resolveNext.call($self, await);
willDone.resolve();
});
}
$self.fadeOut(speed/2, reveal);
});
return willDone.promise(this);
};
$.fn.messager = function(message, speed) {
speed = speed || 500;
if ($.isArray(message)) {
var arr = [];
message.forEach(function(m) {
arr.push(display.call(this, m, speed));
}, this);
return $.when.apply(this, arr);
} else {
return display.call(this, message, speed);
}
}
}( jQuery ));
function play() {
$('#msgbox1').messager(['A demo of', 'queued messages'], 1000);
for (var i = 3; i > 0; i--) $('#msgbox1').messager(i);
$('#msgbox1').messager(['Ready to sing...', 'Singing...']);
for (var i = 8; i > 0; i--) $('#msgbox2').messager('***');
for (i = 1; i < 8; i++) $('#msgbox2').messager(String.fromCharCode(64 + i));
$('#msgbox2')
.messager('')
.done(function() {
$('#msgbox1')
.messager(['End of demo.', 'Thank you.', 'Run again?'], 1000)
.done(function() {
$('#msgbox1, #msgbox2').one('click', play);
$('#msgbox2').messager('>');
});
});
}
play();
html {
background: rgba(0, 0, 0, 0.25);
}
#msgbox1, #msgbox2 {
color: #FFF;
padding: 0.3em 0.5em;
font-size: 36pt;
text-align: center;
height: 1.8em;
cursor: default;
}
#msgbox2 {
color: yellow;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Queuing Messages with jQuery Deferred Object</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div id="msgbox1"></div>
<div id="msgbox2"></div>
</body>
</html>
Edit, Updated
Try
(function ($) {
$.fn.messager = messager;
function messager(message, speed, callback) {
var that = $(this);
if (that.data("queue") === undefined) {
$.fx.interval = 0;
that.data("queue", []);
that.data("msg", []);
};
var q = that.data("queue"),
msgs = that.data("msg");
q.push([message, speed, callback]);
msgs.push(message);
var fn = function (m, s, cb) {
return that.fadeOut(s, function () {
that.text(m)
})
.delay(s)
.fadeIn(s, cb)
.promise("fx")
.done(function (el) {
console.log("callback", q.length);
if (q.length > 1) {
q.splice(0, 1);
fn.apply(el, q[0])
} else {
el.data("queue", []);
console.log("done", el.data("queue").length);
always(promise, ["complete", msgs])
.then(complete);
}
return el.promise("fx");
})
}
, promise = $.when(!that.queue("fx").length
? fn.apply(that, q[0])
: that.promise("fx"))
, always = function (elem, args) {
if (elem.state() === "pending") {
console.log(elem.state(), args)
} else {
if (elem.state() === "resolved") {
elem.done(function (elem) {
console.log(msgs.length + " messages complete");
})
};
};
return elem.promise("fx")
};
always(promise, ["start", message, q.length]);
return that
};
}(jQuery));
See .promise()
(function ($) {
$.fn.messager = messager;
function messager(message, speed, callback) {
var that = $(this);
if (that.data("queue") === undefined) {
$.fx.interval = 0;
that.data("queue", []);
that.data("msg", []);
};
var q = that.data("queue"),
msgs = that.data("msg");
q.push([message, speed, callback]);
msgs.push(message);
var fn = function (m, s, cb) {
return that.fadeOut(s, function () {
that.text(m)
})
.delay(s)
.fadeIn(s, cb)
.promise("fx")
.done(function (el) {
console.log("callback", q.length);
if (q.length > 1) {
q.splice(0, 1);
fn.apply(el, q[0])
} else {
el.data("queue", []);
console.log("done", el.data("queue").length);
always(promise, ["complete", msgs])
.then(complete);
}
return el.promise("fx");
})
}
, promise = $.when(!that.queue("fx").length
? fn.apply(that, q[0])
: that.promise("fx"))
, always = function (elem, args) {
if (elem.state() === "pending") {
console.log(elem.state(), args)
} else {
if (elem.state() === "resolved") {
elem.done(function (elem) {
console.log(msgs.length + " messages complete");
})
};
};
return elem.promise("fx")
};
always(promise, ["start", message, q.length]);
return that
};
}(jQuery));
var complete = function() {
if (!$("pre").is("*")) {
$("body").append("<pre>" + JSON.stringify($(this).data("msg"), null, 4))
} else {
$("pre")
.text(JSON.stringify($(this).data("msg"), null, 4));
$("label[for=messages]").text("messages updated")
.show(0).delay(350).hide(0)
};
};
var fx = function() {
$(this).css("color", "purple").animate({
fontSize: "72"
}, 100, function() {
$(this).animate({
fontSize: "36"
}, 100, function() {
$(this).css("color", "inherit")
})
})
};
var input = $("input");
var $elem = $("#messages");
$elem.messager("0", 1000)
.messager("1", 100)
.messager("2", 200)
.messager("3", 300)
.messager("4", 400)
.messager("5", 500)
.messager("6", 600)
.messager("7", 700)
.messager("8", 800)
.messager("9", 900);
$.each("abcdefghijklmnopqrstuvwxyz".split(""), function(key, val) {
$elem.messager(val, 200, fx);
});
$("button").on("click", function() {
$elem.messager(input.val().length > 0 ? input.val() : $.now(), 200);
input.val("")
});
#messages {
display:block;
height:38px;
font-size:36px;
position : absolute;
}
label[for=messages] {
color:blue;
}
pre {
position:relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<label for="button">add messages</label> <label for="messages"></label><br>
<input type="text" /><button>click</button>
<br />
<div id="messages">messages</div>
<br><br>
<script>
(function ( $ ) {
$.fn.messager = function(message, effect, speed, gothru) {
if (!$(this).data('message'))
{
$(this).data('message', Array());
}
$(this).data('message').push({messageContent: message, messageEffect: effect, messageSpeed: speed});
if ($(this).data('message').length > 1 && gothru != true)
{
return;
}
$(this).fadeOut(speed).delay(speed).text(message).fadeIn(speed, function(){
$(this).data("message").shift();
if ($(this).data('message').length > 0)
{
var arrMessage = $(this).data('message');
var messageContent = arrMessage[0].messageContent;
var messageEffect= arrMessage[0].messageEffect;
var messageSpeed= arrMessage[0].messageSpeed;
$(this).data("message").shift();
$(this).messager(messageContent , messageEffect, messageSpeed, true);
}
});
return this;
};
}( jQuery ));
</script>
It's good now.
The naive way of doing it recursively:
Make a global variable(boolean), in this case called queue. If queue is false, set it to true and begin executing the code you want to run. When that code finishes, set queue back to false. Otherwise, if queue was true, just recursively call _this.messager() until queue is set back to false, which would mean that the code is finished running.
fadeIn() and fadeOut() can take callbacks as the final argument, so I'm utilizing that here.
HTML:
<div id="messageBox"></div>
javaScript:
(function ( $ ) {
var queue = false;
$.fn.messager = function(message, effect, speed) {
var _this = $(this);
if (!queue) {
queue = true;
_this.fadeOut(speed, function() {
_this.text(message);
_this.fadeIn(speed, function() {
queue = false;
});
});
} else {
_this.messager(message, effect, speed);
}
return this;
};
}( jQuery ));
$('#messageBox').messager('One', 300);
$('#messageBox').messager('Two', 300);
$('#messageBox').messager('Three', 300);
This typically results in:
Uncaught RangeError: Maximum call stack size exceeded
A more advanced example:
Here we create a second variable called counter to keep track of how many times 'messager' is called recursively and doesn't exceed the limit specified in the options. I set a default of 50, which can be overwritten by the options parameter.
In addition, we've separated out the code that you want to run. This could even be multiple functions that call each other, the important bit is making sure that when your code is finished running, you set queue to false rather than returning false and setting queue to the result of the function. Setting it to the result of the function just makes it undefined until the function finishes returning. We want it to remain as true until the code is finished executing.
This example also throttles the recursive calling so that it's only called once every 100 milliseconds, although that too can be overwritten with whatever value you like (in milliseconds) via the options parameter.
HTML:
<div id="messageBox"></div>
javaScript:
(function( $ ) {
var queue = false;
var counter = 0;
$.fn.messager = function(message, effect, speed, options) {
var _S = $.extend({
throttle: 100,
counter: 50
}, options);
var _this = $(this);
counter += 1;
function codeToRun() {
_this.fadeOut(speed, function() {
_this.text(message);
_this.fadeIn(speed, function() {
queue = false;
});
});
}
if (!queue) {
queue = true;
codeToRun();
counter = 0;
} else {
if (counter < _S.counter) {
setTimeout(function() {
_this.messager(message, effect, speed);
}, _S.throttle);
}
}
return this;
};
})( jQuery );
$('#messageBox').messager('One', 300);
$('#messageBox').messager('Two', 300);
$('#messageBox').messager('Three', 300);
For some reason, calling methods on $(this) directly gives me:
[Window, jquery: "1.11.0", constructor: function, selector: "", toArray: function, get: function…]
But storing $(this) in a variable and calling methods on that variable gives me the correct element:
[div#messageBox, selector: "#messageBox", context: document, jquery: "1.11.0", constructor: function, toArray: function…]

I can't target the correct scope inside my closure

I have a setInterval inside my closure but my I'm unable to target the variables inside the closure correctly. How so I target the variables correctly and stop the interval when counter has reached the finishTime?
var counter = function() {
return {
myInterval: null,
counter: 0,
finishTime: 1000,
startTimer: function() {
this.myInterval = setInterval(this.repeat,10);
},
repeat: function() {
if(this.counter==this.finishTime) clearInterval(this.myInterval);
this.counter++;
}
}
};
counter().startTimer();
var counter = function() {
return {
myInterval: null,
counter: 0,
finishTime: 1000,
startTimer: function() {
this.myInterval = setInterval(this.repeat.bind(this), 10);//need to bind the context here
},
repeat: function() {
if(this.counter==this.finishTime)
{
clearInterval(this.myInterval);
console.log("finished")
}
this.counter++;
}
}
};
counter().startTimer();
Define everything in local scope (you may optionally assign repeat to the returned object, if you want to access it also from outside):
var Counter = function(finish)
{
var count = 0;
var myInterval = null;
function repeat() {
if(++count == finish)
clearInterval(myInterval);
}
return {
getCount: function() { return count; },
startTimer: function() { myInterval = setInterval(repeat,10); return this; },
repeat: repeat
};
};
var counter = new Counter(1000).startTimer();

Why doesn't this simple JavaScript increment and decrement method work?

(function() {
var count = {
digit: 0,
increment: function() {
var interval = setInterval(function() {
if (++count.digit == 10) {
clearInterval(interval);
count.decrement();
}
var update = document.getElementById("liveUpdate");
update.innerHTML = count.digit;
}, 500);
},
decrement: function() {
var interval = setInterval(function() {
if (--count.digit == -1) {
clearInterval(interval);
}
}, 500);
}
};
count.increment();
})();
It stops but it doesn't go down? What could be the problem?
Your decrement function never updates the output anywhere. The variable is going down but you don't show that on screen.
Try (or check the corresponding JSFiddle):
(function() {
var update = document.getElementById("liveUpdate");
var count = {
digit: 0,
increment: function() {
var interval = setInterval(function() {
if (++count.digit == 10) {
clearInterval(interval);
count.decrement();
}
update.innerHTML = count.digit;
}, 500);
},
decrement: function() {
var interval = setInterval(function() {
if (--count.digit == -1) {
clearInterval(interval);
}
update.innerHTML = count.digit;
}, 500);
}
};
count.increment();
})();
setInterval will call the function every 500 seconds. It will not stop until you stop it. You can read more about stopping it at Stop setInterval call in JavaScript
It't not a bug, it's a feature ;-). setInterval() runs the given function in a loop with a given interval (500 ms). See this article for details.

Categories