Hi I'm making a pomodoro clock. I want to allow the timer to increase or decrease every 100 milliseconds when the user holds down the button. The running conditions for mousedown and clickUpdate are very similar.
The entire code of clickUpdate relies on using this keyword to achieve that goal. But how can I let setInterval inherit or have access to this keyword? This referring to the button object that mousedown is a method of.
https://codepen.io/jenlky/pen/ypQjPa?editors=0010
var timer;
const session = document.getElementById("session");
const breaktime = document.getElementById("break");
const mins = document.getElementById("mins");
const secs = document.getElementById("secs");
function clickUpdate () {
// if data-action = increase and its under session-input, increase session.value else increase breaktime.value
if (this.dataset.action === "increase") {
if (this.parentElement.className === "session-input") {
// if session.value is 60 mins, this increase click changes it to 1 min
if (session.value === "60") {
session.value = 1;
} else {
session.value = Number(session.value) + 1;
}
mins.innerText = session.value;
// if breaktime.value is 60 mins, this increase click changes it to 1 min
} else {
if (breaktime.value === "60") {
breaktime.value = 1;
} else {
breaktime.value = Number(breaktime.value) + 1;
}
}
}
// if data-action = decrease and its under session-input, decrease session.value else decrease breaktime.value
if (this.dataset.action === "decrease") {
if (this.parentElement.className === "session-input") {
// if session.value is 1 min, this decrease click changes it to 60 mins
if (session.value === "1") {
session.value = 60;
} else {
session.value = Number(session.value) - 1;
}
mins.innerText = session.value;
// if breaktime.value is 1 min, this decrease click changes it to 60 mins
} else {
if (breaktime.value === "1") {
breaktime.value = 60;
} else {
breaktime.value = Number(breaktime.value) - 1;
}
}
}
console.log(this);
}
// Problem is how can I let clickUpdate or setInterval(function(){},100) inherit this
// setInterval's function doesn't seem to inherit or have any parameters
// I'm not sure how forEach thisArg parameter works, or how to use bind, or how to use addEventListener last parameter
function mousedown() {
var obj = this;
timer = setInterval(clickUpdate, 100);
}
function mouseup() {
if (timer) {
clearInterval(timer);
}
}
const buttons = Array.from(document.getElementsByClassName("symbol"));
mins.innerText = session.value;
//buttons.forEach(button => button.addEventListener("click", clickUpdate));
buttons.forEach(button => button.addEventListener("mousedown", mousedown));
buttons.forEach(button => button.addEventListener("mouseup", mouseup));
console.log(session);
The this value within functions defined using function () ... is usually dependent on how the function is called. If you call a.myFunction() then this within the function will be a reference to a. If you call myFunction(), then this will either be undefined or the global object depending on whether you are using strict or sloppy mode.
Usually the most straightforward way to get a callback function to use a particular this value is to use .bind(n). This basically creates a wrapped version of the original function, with the this value locked in as n
setInterval(clickUpdate.bind(this), 100);
Then again, my preference would be to not use this at all. It's confusing and has wacky behavior that often requires all sorts of contrived workarounds (as you have experienced). You could just as easily pass in the value as a parameter here:
function mousedown(e) {
timer = setInterval(clickUpdate, 100, e.target);
}
One trick is to create a separate reference (either global or at least on a common ancestor scope) holding the reference to the "this" reference from the level you want.
Once you do that, you can refer to it within the setInterval function.
Example:
var scope = this;
this.name = 'test';
setInterval(function() {
console.log(scope.name); // should print 'test' every 1 second
},1000);
Related
I have a function that when called will decrease by 1. It is called when a user reports something. I want to be able to store this and then when it hits 0, to execute an action.
function userReported() {
console.log('user report ' + add());
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
Now the problem is I can return the counter so it logs down from 10. But the issue I have is that I can seem to add an if/else before returning counter as it does not store the variable.
I attempted the following but it doesn't work and I don't know how to return something > store it, and at the same time check its value. I also tried a while loop but failed too.
function userReported() {
var limit = add;
if ( limit <= 0 ) {
console.log('Link does not work!');
}
else {
console.log('user report ' + limit);
}
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
How do I go about creating a value, increment/decrement said value, then when it reaches a number -> do something?
You would typically do this with a function that returns a function that captures the counter in a closure. This allows the returned function to maintain state over several calls.
For example:
function createUserReport(limit, cb) {
console.log('user report initiated' );
return function () {
if (limit > 0) {
console.log("Report filed, current count: ", limit)
limit--
} else if (limit == 0) {
limit--
cb() // call callback when done
}
// do something below zero?
}
}
// createUserReport takes a limit and a function to call when finished
// and returns a counter function
let report = createUserReport(10, () => console.log("Reached limit, running done callback"))
// each call to report decrements the limit:
for (let i = 0; i <= 10; i++){
report()
}
You can of course hard-code the callback functionality and limit number into the function itself rather than passing in arguments.
Ok, if you need to get a report based on an external limit, you could do something like that:
var limit = 10;
function remove() {
limit -= 1;
}
function userReport() {
if (limit <= 0) {
console.log("Link does not work!");
} else {
remove();
console.log(`User report: ${limit}`);
}
}
userReport();
If that's what you want, removing the remove function from userReport and taking the limit variable out will make things work
I have a class that takes some coordinate and duration data. I want to use it to animate an svg. In more explicit terms, I want to use that data to change svg attributes over a time frame.
I'm using a step function and requestAnimationFrame outside the class:
function step(timestamp) {
if (!start) start = timestamp
var progress = timestamp - start;
var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
var moveX = distancePerFrame(circleMove.totalFrames(), circleMove.xLine);
document.querySelector('#start').setAttribute('cx', currentX + moveX);
if (progress < circleMove.duration) {
window.requestAnimationFrame(step);
}
}
var circleMove = new SingleLineAnimation(3000, startXY, endXY)
var start = null
function runProgram() {
window.requestAnimationFrame(step);
}
I can make it a method, replacing the circleLine with this. That works fine for the first run through, but when it calls the this.step callback a second time, well, we're in a callback black hole and the reference to this is broken. Doing the old self = this won't work either, once we jump into the callback this is undefined(I'm not sure why). Here it is as a method:
step(timestamp) {
var self = this;
if (!start) start = timestamp
var progress = timestamp - start;
var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
var moveX = distancePerFrame(self.totalFrames(), self.xLine);
document.querySelector('#start').setAttribute('cx', currentX + moveX);
if (progress < self.duration) {
window.requestAnimationFrame(self.step);
}
}
Any ideas on how to keep the "wiring" inside the Object?
Here's the code that more or less works with the step function defined outside the class.
class SingleLineAnimation {
constructor(duration, startXY, endXY) {
this.duration = duration;
this.xLine = [ startXY[0], endXY[0] ];
this.yLine = [ startXY[1], endXY[1] ];
}
totalFrames(framerate = 60) { // Default to 60htz ie, 60 frames per second
return Math.floor(this.duration * framerate / 1000);
}
frame(progress) {
return this.totalFrames() - Math.floor((this.duration - progress) / 17 );
}
}
This will also be inserted into the Class, for now it's just a helper function:
function distancePerFrame(totalFrames, startEndPoints) {
return totalFrames > 0 ? Math.floor(Math.abs(startEndPoints[0] - startEndPoints[1]) / totalFrames) : 0;
}
And click a button to...
function runProgram() {
window.requestAnimationFrame(step);
}
You need to bind the requestAnimationFrame callback function to a context. The canonical way of doing this is like this:
window.requestAnimationFrame(this.step.bind(this))
but it's not ideal because you're repeatedly calling .bind and creating a new function reference over and over, once per frame.
If you had a locally scoped variable set to this.step.bind(this) you could pass that and avoid that continual rebinding.
An alternative is this:
function animate() {
var start = performance.now();
el = document.querySelector('#start');
// use var self = this if you need to refer to `this` inside `frame()`
function frame(timestamp) {
var progress = timestamp - start;
var currentX = parseInt(el.getAttribute('cx'));
var moveX = distancePerFrame(circleMove.totalFrames(), circleMove.xLine);
el.setAttribute('cx', currentX + moveX);
if (progress < circleMove.duration) {
window.requestAnimationFrame(frame);
}
}
window.requestAnimationFrame(frame);
}
i.e. you're setting up the initial state, and then doing the animation within a purely locally scoped function that's called pseudo-recursively by requestAnimationFrame.
NB: either version of the code will interact badly if you inadvertently call another function that initiates an animation at the same time.
this file
http://www.iguanademos.com/Jare/docs/html5/Lessons/Lesson2/js/GameLoopManager.js
taken from this site
Here is the code:
// ----------------------------------------
// GameLoopManager
// By Javier Arevalo
var GameLoopManager = new function() {
this.lastTime = 0;
this.gameTick = null;
this.prevElapsed = 0;
this.prevElapsed2 = 0;
I understand the declaration of variables,
and they are used to record the time between frames.
this.run = function(gameTick) {
var prevTick = this.gameTick;
this.gameTick = gameTick;
if (this.lastTime == 0)
{
// Once started, the loop never stops.
// But this function is called to change tick functions.
// Avoid requesting multiple frames per frame.
var bindThis = this;
requestAnimationFrame(function() { bindThis.tick(); } );
this.lastTime = 0;
}
}
I don't understand why he uses var bindThis = this
this.stop = function() {
this.run(null);
}
This function set's gameTick to null, breaking the loop in this.tick function.
this.tick = function () {
if (this.gameTick != null)
{
var bindThis = this;
requestAnimationFrame(function() { bindThis.tick(); } );
}
else
{
this.lastTime = 0;
return;
}
var timeNow = Date.now();
var elapsed = timeNow - this.lastTime;
if (elapsed > 0)
{
if (this.lastTime != 0)
{
if (elapsed > 1000) // Cap max elapsed time to 1 second to avoid death spiral
elapsed = 1000;
// Hackish fps smoothing
var smoothElapsed = (elapsed + this.prevElapsed + this.prevElapsed2)/3;
this.gameTick(0.001*smoothElapsed);
this.prevElapsed2 = this.prevElapsed;
this.prevElapsed = elapsed;
}
this.lastTime = timeNow;
}
}
}
Most of this code is what I don't understand, I can see he is recording the time elapsed between frames, but the rest of the code is lost to me.
On the website he uses the term singleton, which is used to prevent the program trying to update the same frame twice?
I have a bit of experience with the javascript syntax, but the concepts of singleton, and the general goal/function of this file is lost to me.
Why is the above code needed instead of just calling
requestAnimationFrame(function() {} );
The reason he uses bindThis is that he is passing a method into an anonymous function on the next line. If he merely used this.tick(), this would be defined as the context of requestAnimationFrame. He could achieve the same thing by using call or apply.
Singletons are classes that are only instantiated once. This is a matter of practice, and not a matter of syntax - javascript doesn't know what a singleton is. By calling it a "Singleton", he is merely communicating that this is a class that is instantiated only once, and everything that needs it will reference the same instance.
I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}
Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?
I've used code like your first block, and it works fine. Also this jsFiddle works as well.
I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}
You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);
Simple question here that I can't seem to find an answer for: Once a setTimeout is set, is there any way to see if it's still, well, set?
if (!Timer)
{
Timer = setTimeout(DoThis,60000);
}
From what I can tell, when you clearTimeout, the variable remains at its last value. A console.log I just looked at shows Timer as being '12', no matter if the timeout has been set or cleared. Do I have to null out the variable as well, or use some other variable as a boolean saying, yes, I have set this timer? Surely there's a way to just check to see if the timeout is still running... right? I don't need to know how long is left, just if it's still running.
What I do is:
var timer = null;
if (timer != null) {
window.clearTimeout(timer);
timer = null;
}
else {
timer = window.setTimeout(yourFunction, 0);
}
There isn't anyway to interact with the timer except to start it or stop it. I typically null the timer variable in the timeout handler rather than use a flag to indicate that the timer isn't running. There's a nice description on W3Schools about how the timer works. In their example they use a flag variable.
The value you are seeing is a handle to the current timer, which is used when you clear (stop) it.
There is no need to check for an existing timer, just execute clearTimeout before starting the timer.
var timer;
//..
var startTimer = function() {
clearTimeout(timer);
timer = setTimeout(DoThis, 6000);
}
This will clear any timer before starting a new instance.
Set another variable Timer_Started = true with your timer. And also change the variable to false when the timer function is called:
// set 'Timer_Started' when you setTimeout
var Timer_Started = true;
var Timer = setTimeout(DoThis,60000);
function DoThis(){
// function DoThis actions
//note that timer is done.
Timer_Started = false;
}
function Check_If_My_Timer_Is_Done(){
if(Timer_Started){
alert("The timer must still be running.");
}else{
alert("The timer is DONE.");
}
}
I know this is a necroposting but i think still people are looking for this.
This is what i use:
3 variables:
t for milliseconds since.. in Date Object for next target
timerSys for the actual interval
seconds threshold for milliseconds has been set
next i have a function timer with 1 variable the function checks if variable is truly, if so he check if timer is already running and if this is the case than fills the global vars , if not truly, falsely, clears the interval and set global var timerSys to false;
var t, timerSys, seconds;
function timer(s) {
if (s && typeof s === "number") {
if (typeof timerSys === "boolean" || typeof timerSys === "undefined") {
timerSys = setInterval(function() {
sys();
}, s);
t = new Date().setMilliseconds(s);
seconds = s;
}
} else {
clearInterval(timerSys);
timerSys = false;
}
return ((!timerSys) ? "0" : t)
}
function sys() {
t = new Date().setMilliseconds(seconds);
}
Example I
Now you can add a line to sys function:
function sys() {
t = new Date().setMilliseconds(seconds);
console.log("Next execution: " + new Date(t));
//this is also the place where you put functions & code needed to happen when interval is triggerd
}
And execute :
timer(5000);
Every 5 seconds in console:
//output:: Next execution: Sun May 08 2016 11:01:05 GMT+0200 (Romance (zomertijd))
Example II
function sys() {
t = new Date().setMilliseconds(seconds);
console.log("Next execution: " + seconds/1000 + " seconds");
}
$(function() {
timer(5000);
});
Every 5 seconds in console:
//output:: Next execution: 5 seconds
Example III
var t, timerSys, seconds;
function timer(s) {
if (s && typeof s === "number") {
if (typeof timerSys === "boolean" || typeof timerSys === "undefined") {
timerSys = setInterval(function() {
sys();
}, s);
t = new Date().setMilliseconds(s);
seconds = s;
}
} else {
clearInterval(timerSys);
timerSys = false;
}
return ((!timerSys) ? "0" : t)
}
function sys() {
t = new Date().setMilliseconds(seconds);
console.log("Next execution: " + seconds / 1000 + " seconds");
}
$(function() {
timer(5000);
$("button").on("click", function() {
$("span").text(t - new Date());
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Freebeer</button>
<span></span>
Note this way you can go below 0
I usually nullify the timer:
var alarm = setTimeout(wakeUpOneHourLater, 3600000);
function wakeUpOneHourLater() {
alarm = null; //stop alarm after sleeping for exactly one hour
}
//...
if (!alarm) {
console.log('Oops, waked up too early...Zzz...');
}
else {
console.log('Slept for at least one hour!');
}