I have a toast notification system which displays a notification for 10 seconds before fading out. I want to add a functionality that pauses the countdown for fading out when the notification is hovered, and resumes when no longer hovering.
I'm trying to use the setInterval() function to do this, but I'm unsure how to later pause (clear) this interval. I know I can bind the setInterval to a variable, but these notifications are created dynamically, so I cannot bind them to a single variable.
In the example below, I store the setInterval() in a variable named ???, I would ideally bind this interval using the jQuery element ($(this)) as a variable so it's always unique, and can easily be cleared by passing the same jQuery element through the clearInterval() function.
Is there any way of doing this, or am I going about building this system all wrong?
// Start notification countdown
$.countDownNotification = function(notification) {
// IMPORTANT: Store the setInterval in a element-specific variable?
var ??? = setInterval( function() {
// Counts down from remaining seconds
var remaining = notification.attr('data-timer') + 1 - 1;
// Stores remaining seconds in data-attribute
notification.attr('data-timer', remaining);
// Remove notification when timer is on 0
if ( remaining == 0 ) {
notification.remove();
}
}, 1000);
}
// Pause on hover
$('.notification').on('mouseenter', function(e) {
// IMPORTANT: Clear the elemnt-specific interval
clearInterval(???);
});
// Resume when hover ends
$('.notification').on('mouseleave', function(e) {
var notification = $(this)
$.countDownNotification(notification);
});
You can store the interval on the notification via .data().
notification.data('int', setInterval(...
Then, in the event callbacks you can reference the interval via
$(this).data('int')
Also, note + 1 - 1 doesn't do anything meaningful.
Consider declaring a global variable.
// Start notification countdown
$.countDownNotification = function(notification) {
// IMPORTANT: Store the setInterval in a element-specific variable?
timer = setInterval( function() {
// Counts down from 10 and stores new value in data-attribute
notification.attr('data-timer', notification.attr('data-timer') - 1);
}, 1000);
// Remove notification when timer is on 0
if ( newRemaining == 0 ) {
notification.remove();
}
}
// `false` means no timer has been set
var timer = false;
// Pause on hover
$('.notification').on('mouseenter', function(e) {
// IMPORTANT: Clear the elemnt-specific interval
clearInterval( timer );
});
// Resume when hover ends
$('.notification').on('mouseleave', function(e) {
var notification = $(this)
$.countDownNotification(notification);
});
Another way to not set a global object is to return setInterval() by .countDownNotification.
// Start notification countdown
$.countDownNotification = function(notification) {
// IMPORTANT: Store the setInterval in a element-specific variable?
var id = setInterval( function() {
// Counts down from 10 and stores new value in data-attribute
notification.attr('data-timer', notification.attr('data-timer') - 1);
}, 1000);
// Remove notification when timer is on 0
if ( newRemaining == 0 ) {
notification.remove();
}
return id;
}
( function() {
// `false` means no timer has been set
var timer = false;
// Pause on hover
$('.notification').on('mouseenter', function(e) {
// IMPORTANT: Clear the elemnt-specific interval
clearInterval( timer );
});
// Resume when hover ends
$('.notification').on('mouseleave', function(e) {
var notification = $(this)
timer = $.countDownNotification(notification);
});
})();
Related
I was wondering if there is a nicer object oriented way of creating this timer? (without global vars!)
let secondsPassed = 0;
let timerId;
function startTimer() {
clearInterval(timerId);
timerId = setInterval(function() {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
$(window).blur(function() {
clearInterval(timerId) // stop timer when user leaves tab
});
$(window).focus(function() {
startTimer(); // continue timer when user comes back
});
}
Your current implementation is actually wrong. Every time you call startTimer, it installs startTimer as a new window focus event handler, leading to multiple started intervals when you focus the window the second time; growing exponentially. The onfocus handler should only run the timerId = setInterval(…) line - put that in a nested helper function to call only that.
This also makes it unnecessary to declare the variables globally.
function createTimer() {
let secondsPassed = 0;
let timerId;
function resume() {
if (timerId) return; // prevent multiple intervals running at the same time
timerId = setInterval(() => {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
}
function pause() {
clearInterval(timerId);
timerId = undefined;
}
$(window).blur(pause); // stop timer when user leaves tab
$(window).focus(resume); // continue timer when user comes back
resume(); // now start the timer
}
Now how to make that object-oriented? Just return an object from createTimer. Put resume and pause as methods on that object. Maybe add some more methods for starting, stopping, resetting, whatever you need. Maybe use a property on the object instead of the secondsPassed local variable. Or expose the local variable using a getter.
And to make it reusable, of course you can make createTimer accept arguments, from the selector of the output element, to the output element itself, to a callback function that will be called with the current time on every tick.
Edit: With this answer, you have to implement the Timer class yourself first. The code only shows how you could name the methods of the timer, how you create the instance and call its functions. The timer should (principle "separation of concerns") only handle the counting and provide the functionalities needed, like starting and stopping.
If you want to have an OOP solution for your timer, you shouldn't let the Timer class know the ID of the DOM container (like one of your comments to your question suggested).
You should read into the topic using this:
https://appdividend.com/2019/05/22/javascript-class-example-how-to-use-class-in-javascript-tutorial/
Let us assume, that you already implemented the class. Your code above should look like the following:
// Create own scope for the function, so that variable are not assigned to windows-object.
(function() {
let secondsPassed = 0;
let timer = new Timer();
// events, if necessary
timer.onTick((seconds) => { secondsPassed = seconds });
timer.onStop(() => { secondsPassed = 0; })
// Called by a button
function startTimer() {
timer.start();
}
// Example: Display alert with current timer seconds on click
function displaySecondsOfTimer() {
alert(timer.getSeconds());
}
$(window).blur(function() {
timer.stop(); // stop timer when user leaves tab
});
$(window).focus(function() {
timer.start(); // continue timer when user comes back
});
})();
So I think, you have a good example to code your first Timer class in native JavaScript! :)
Cannot terminate the setInterval I created in launch. It works until the time is up. I want to use clearInterval (interval) operation in next() function and prev() function. How should I do this? When I click forward, I want clearInterval(interval) to run this, but I couldn't.
function launch() {
thisTimeline = document.getElementsByClassName('story-active-' + start)[0];
var maxtime = 5000;
var incremental = 100;
var actualtime = 0;
var interval = setInterval(function() {
actualtime += incremental;
var percentage = Math.ceil((100 / maxtime) * actualtime);
thisTimeline.style.width = percentage + '%';
if (percentage == 100) {
clearInterval(interval);
thisTimeline.style.width = "0%";
}
}, incremental);
}
function next() {
// Set previous video timeline to 100% complete
thisTimeline.style.width = '100%';
// Advance play count to next video
start++;
// If next video doesn't exist (i.e. the previous video was the last) then close the Social Story popup
if (start >= defaults.playlist.length) {
setTimeout(function() {
close();
return false;
}, 400);
} else {
// Otherwise run the next video
launch(start);
}
}
function prev() {
if (start != 0) {
thisTimeline.style.width = '0%';
}
// Subtract play count to previous video
start--;
// If next video doesn't exist (i.e. the previous video was the last) then close the Social Story popup
if (start < 0) {
start = 0;
return false;
} else {
// Otherwise run the previous video
launch(start);
}
}
This is an extension of #lagoCalazans comment.
What he is saying is that in your variable "interval" is created in your launch function. You need to make "interval" global in order to clear your setInterval.
Ex:
let interval = null; //global
function launch() {
let tempInterval = setInterval(function() {
//whatever code
},100);
interval = setInterval(function(){
console.log("Hello");
}, 100);
}
function clear() {
//Since interval is global I can clear it when I call clear();
clearInterval(interval);
}
As you can see in the launch function "tempInterval" is limited to the scope of launch, therefore cannot be accessed anywhere else, but now since "interval" is global it can be accessed in any function.
Your code seems a bit incomplete, so for illustrative purposes only I will assume you encapsulate those functions in a higher order function (like an IIFE) and will avoid writing that (also, some kind of global state or variable would do for an example).
First of all, setInterval will return an id which you would use later, so if you want to use it within next and prev, you need that value to be available to them.
So, in your example, you should declare interval outside launch, and assign a value to it inside:
let interval
function launch() {
// ...
interval = setInterval(function() { ... })
}
and then use interval wherever you want.
launch, next and prev are three separate functions. They do not reference the same interval because they don't share scope. Raise the scope of the interval variable.
let interval = ''; // declared here, interval can be accessed by all functions
function launch() {
// ...
// remove the var before interval
interval = setInterval( ... )
}
function next() {
// ...
// remove the var before interval
interval = setInterval( ... )
}
function prev() {
// ...
// remove the var before interval
interval = setInterval( ... )
}
I want to run a function after the last mousemove event. I've tried the code below:
#HostListener('document:mousemove', ['event'])
eventHandler(event) {
setTimeout(() => {
// do something
}, 60000);
}
The problem is, it fires in the first mousemove event and won't reset the time if another event occurs. How to reset it and start the setTimeout function each time the event occurs?
I'm not sure which framework you're using, but in general you need to store the Id of the timer and cancel it every time and start a new one - remembering to capture the Id.
var timerId = 0
document.addEventListener("mousemove",function(){
clearTimeout(timerId);
timerId = setTimeout(function(){
console.log("5 seconds since last mouse move");
},5000);
});
Well, when you call setTimeout, the return value is a number, representing the ID value of the timer that is set. Use this value with the clearTimeout() method to cancel the timer and set it again when a new event occurs. You can read more about setTimeout and clearTimeout here and here.
Basically you can have something like:
//global variable
var timeoutID;
[...]
#HostListener('document:mousemove', ['event'])
eventHandler(event) {
if (timeoutID)
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
// do something
}, 60000);
}
I provided a simple jsfiddle (pure js, no framework) too:
var timeoutValue = 2000;
var timeoutID;
document.addEventListener("mousemove", function() {
if (timeoutID)
clearTimeout(timeoutID);
timeoutID = setTimeout(function() {
console.log(timeoutValue + " just passed");
}, 2000);
});
Cheers!
Is it possible to force setInterval to go to next step like this?:
global.timers = [] ;
var index = global.timers.length ;
global.timers[index] = setInterval(()=>{
// ...
},15000);
// pseudo code
if (event1 occured)
global.timers[x].step++ // Don't wait for remained time and go to next tick
You could simply clear the interval and create it again
global.timers = [] ;
var index = global.timers.length ;
global.timers[index] = setInterval(fn,15000);
// pseudo code
if (event1 occured) {
clearInterval(global.timers[x]);
global.timers[index] = setInterval(fn,15000);
}
function fn() {
}
You could store the id in the timers array and extract the event handler function then you would have more control over when to call the event handler.
Create a handler function for the event
When pushing to the global timers array, remember to store a name and the timer id.
If the event occured before the timer was called through setInterval, stop the timer and call the handler directly.
Example:
function coolEventHandler () {
console.log('handling cool event');
}
timers[0] = { name: 'coolEvent', id: setInterval(coolEventHandler, 15000) };
if('coolEvent') {
// find the correct timer
timer = timers.find(t => t.name === 'coolEvent');
// stop the timer so the callback is never invoked
// you probably want to remove it from the global timers array here too.
clearInterval(timer.id);
// call the handler directly
coolEventHandler();
}
I would like to fire an AJAX request if an input stabilized (in order to not send a request after each new character). I tried the following:
$('#input').keyup(function(){
// Get the value when keyup is fired
value = $('#input').val();
setTimeout(function(){
// If new value after 1 second is the same that the previous value
if( value == $('#input').val() ){
do_stuff();
}
}, 1000); // Wait 1 second to stabilize
});
But I ended up with a script that waits for a stabilization of 1 second, before firing the request many times and not only once.
Do you have an idea of a better script, or maybe there is a jQuery method that do the trick?
Thanks
you have to clear timeout if user enter again some text like this:
var Timeout; // initialize a variable
$(document).ready(function () {
$('input#input').keypress(function () {
var txtbox = $(this); // copy of this object for further usage
if (Timeout) //check if timeout not null
clearTimeout(Timeout); // not null so clear it
Timeout = setTimeout(function () { // set timeout for 1 second
alert(txtbox.val());
}, 1000);
});
});
FIDDLE DEMO
The trick is to cancel the timeout when something changed in the meantime.
var timer = null;
$('#input').keyup(function() {
// Get the value when keyup is fired
var value = $('#input').val();
// Reset timeout
clearTimeout(timer);
timer = setTimeout(function() {
// If new value after 1 second is the same that the previous value
if (value === $('#input').val()) {
do_stuff();
}
}, 1000); // Wait 1 second to stabilize
});
Just set a variable to the timeout and clear it before setting a new timeout
$('#input').keyup((function() {
var timeout;
return function(){
if (timeout) {
clearTimeout(timeout);
}
// Get the value when keyup is fired
value = $('#input').val();
timeout = setTimeout(function(){
// If new value after 1 second is the same that the previous value
if( value == $('#value').val() ){
do_stuff();
}
}, 1000); // Wait 1 second to stabilize
};
})());