Hi I am having a problem where scoping seems to be lost. What I am doing wrong?
Suppose the logic is that 1 second is decreased from every counter and not from the last counter only.
What I am doing wrong?
<html>
<head>
<link rel="stylesheet" href="http://static.jquery.com/files/rocker/css/reset.css" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
(function($){
$.fn.extend({
countdown: function(options) {
var defaults = {
daysSelector : 'span.days',
hoursSelector : 'span.hours',
minutesSelector : 'span.minutes',
secondsSelector : 'span.seconds'
}
var options = $.extend(defaults, options);
var _this = $(this);
tick = function()
{
var days = _this.find(options.daysSelector);
var hours = _this.find(options.hoursSelector);
var minutes = _this.find(options.minutesSelector);
var seconds = _this.find(options.secondsSelector);
console.log(_this);
var currentSeconds=seconds.text();
currentSeconds--;
if(currentSeconds<0)
{
seconds.text("59");
var currentMinutes=minutes.text();
currentMinutes--;
if(currentMinutes<0)
{
(minutes).text("59");
var currentHours=(hours).text();
currentHours--;
if(currentHours<0)
{
(hours).text("23");
var currentDays=(hours).text();
currentDays--;
}
else
{
if(currentHours.toString().length==1)
{
(hours).text('0'+currentHours);
}
else
{
(hours).text(currentHours);
}
}
}
else
{
if(currentMinutes.toString().length==1)
{
(minutes).text('0'+currentMinutes);
}
else
{
(minutes).text(currentMinutes);
}
}
}
else
{
if(currentSeconds.toString().length==1)
{
seconds.text('0'+currentSeconds);
}
else
{
seconds.text(currentSeconds);
}
}
}
return this.each(function()
{
console.log(_this);
setInterval("this.tick()",1000);
});
}
});
})(jQuery);
$(document).ready(function()
{
$("#timer1").countdown();
$("#timer2").countdown();
$("#timer3").countdown();
});
</script>
</head>
<body>
<div id="timer1">
<span class="days">1</span>
<span class="hours">18</span>
<span class="minutes">6</span>
<span class="seconds">45</span>
</div>
<div id="timer2">
<span class="days">2</span>
<span class="hours">28</span>
<span class="minutes">1</span>
<span class="seconds">59</span>
</div>
<div id="timer3">
<span class="days">10</span>
<span class="hours">0</span>
<span class="minutes">59</span>
<span class="seconds">59</span>
</div>
</body>
I edited your code
(function($){
$.fn.extend({
countdown: function(options) {
var defaults = {
daysSelector : 'span.days',
hoursSelector : 'span.hours',
minutesSelector : 'span.minutes',
secondsSelector : 'span.seconds'
}
var options = $.extend(defaults, options);
var _this = $(this);
var tick = function()
{
var days = _this.find(options.daysSelector);
var hours = _this.find(options.hoursSelector);
var minutes = _this.find(options.minutesSelector);
var seconds = _this.find(options.secondsSelector);
console.log(_this);
var currentSeconds=seconds.text();
currentSeconds--;
if(currentSeconds<0)
{
seconds.text("59");
var currentMinutes=minutes.text();
currentMinutes--;
if(currentMinutes<0)
{
(minutes).text("59");
var currentHours=(hours).text();
currentHours--;
if(currentHours<0)
{
(hours).text("23");
var currentDays=(hours).text();
currentDays--;
}
else
{
if(currentHours.toString().length==1)
{
(hours).text('0'+currentHours);
}
else
{
(hours).text(currentHours);
}
}
}
else
{
if(currentMinutes.toString().length==1)
{
(minutes).text('0'+currentMinutes);
}
else
{
(minutes).text(currentMinutes);
}
}
}
else
{
if(currentSeconds.toString().length==1)
{
seconds.text('0'+currentSeconds);
}
else
{
seconds.text(currentSeconds);
}
}
}
return _this.each(function()
{
console.log(_this);
setInterval(tick,1000);
});
}
});
})(jQuery);
$(document).ready(function()
{
$("#timer1").countdown();
$("#timer2").countdown();
$("#timer3").countdown();
});
tick was global and that was the problem (also never pass code to be evalued to setInterval!
Fiddle here: http://jsfiddle.net/kvqWR/1/
If I have to guess what the issue is... only 1 timer actually counts down and the other two doesn't?
That's because the extended countdown timer function you have never passes the name (id) of the countdown timer through to the function. So it always only executes the last one since the last one is what was called...last.
You'd need to send through the name (id) of the div for it to count down all 3 simultaneously.
You declared tick in global scope. Make it local:
var tick = function()...
and don't pass a string to setInterval:
setInterval(tick,1000);
A better structure of your plugin would be to declare the tick function only once and pass the current element (or the time fields) as argument:
(function($){
var tick = function(days, hours, minutes, seconds) {
//...
};
$.fn.countdown = function(options) {
var defaults = {
daysSelector : 'span.days',
hoursSelector : 'span.hours',
minutesSelector : 'span.minutes',
secondsSelector : 'span.seconds'
}
var options = $.extend(defaults, options);
return this.each(function(){
var days = $(this).find(options.daysSelector);
var hours = $(this).find(options.hoursSelector);
var minutes = $(this).find(options.minutesSelector);
var seconds = $(this).find(options.secondsSelector);
setInterval(function() {
tick(days, hours, minutes, seconds);
},1000);
});
};
}(jQuery));
Then your plugin also works if you select multiple elements at once:
$("#timer1, #timer2, #timer3").countdown();
which would not work otherwise (see here http://jsfiddle.net/fkling/kvqWR/5/).
Working DEMO
It might also be better to have only setInterval going on which iterates over selected elements and performs the operation. The fewer timers you have, the better. Have a look at this example: http://jsfiddle.net/fkling/kvqWR/4/
Related
I am working on a pomodoro clock project where the user can set a session length and a break length so the clock will count down until the user click Stop.
I want the timer to resume after being paused whenever I click Start, but it keeps switching to the other session/break.
I know that I need to calculate the remaining seconds at the time when it stops and then run the startTimer() again.But I haven't been able to figure it out yet.
Any help appreciated! You can scroll down to the //====VIEW====//: startTimer(seconds) to see the logic from there.
Here's my code:
//======MODEL======//
//by default, session lasts 25min, break lasts 5 min
var data = {
session: 25,
break: 5,
isSession: true
}
//===== OCTOPUS=====//
var octopus = {
init: function() {
//initialize the views
settings.init();
timer.init();
},
//========== SETTINGS==========//
//get the time of session and break
getCurrentSession: function() {
return data.session;
},
getCurrentBreak: function() {
return data.break;
},
// set the currently-selected time to the object passed in
setCurrentSession: function(isSession) {
data.isSession = isSession;
},
//increment or decrement the counter
incrementSessionCounter: function() {
data.session++;
settings.render();
},
incrementBreakCounter: function() {
data.break++;
settings.render();
},
decrementSessionCounter: function() {
data.session--;
settings.render();
},
decrementBreakCounter: function() {
data.break--;
settings.render();
}
}
//----- VIEW----//
var settings = {
init: function() {
//display the default session and break with increment and decrement
//listen to click function on either + or - to adjust the timer
this.session = document.getElementById("displaySession");
this.break = document.getElementById("displayBreak");
this.incrementSession = document.getElementById("increaseSession");
this.decrementSession = document.getElementById("decreaseSession");
this.incrementBreak = document.getElementById("increaseBreak");
this.decrementBreak = document.getElementById("decreaseBreak");
this.incrementSession.addEventListener("click", function() {
octopus.incrementSessionCounter();
});
this.decrementSession.addEventListener("click", function() {
octopus.decrementSessionCounter();
});
this.incrementBreak.addEventListener("click", function() {
octopus.incrementBreakCounter();
});
this.decrementBreak.addEventListener("click", function() {
octopus.decrementBreakCounter();
});
this.render();
},
render: function() {
// update the DOM elements with values from the current session and break
var currentSession = octopus.getCurrentSession();
var currentBreak = octopus.getCurrentBreak();
this.session.textContent = currentSession;
this.break.textContent = currentBreak;
}
}
var timer = {
init: function() {
this.startTimer = document.getElementById("start");
this.stopTimer = document.getElementById("stop");
this.nextTimer = document.getElementById("next");
this.render();
},
render: function() {
var displayTimer = document.getElementById("countdown-timer");
this.startTimer.addEventListener("click", function() {
getDeadline();
});
this.nextTimer.addEventListener("click", function() {
getDeadline();
});
this.stopTimer.addEventListener("click", function() {
clearInterval(interval);
delete interval;
})
let interval;
//this function takes in the number of seconds and calculate the seconds left to reach the deadline
function startTimer(seconds) {
const now = Date.now();
const then = now + seconds * 1000;
interval = setInterval(function() {
const secondsLeft = (then - Date.now()) / 1000;
if (secondsLeft < 0) {
clearInterval(interval);
getDeadline();
return;
}
displayTimeLeft(secondsLeft);
}, 1000)
}
//this function takes in the number of leftover secs, convert it into mm:ss and display it on screen
function displayTimeLeft(seconds) {
var minutes = Math.floor(seconds / 60);
var remainderSeconds = Math.round(seconds % 60);
if (remainderSeconds < 10) {
var display = `${minutes}: 0${remainderSeconds}`;
} else
var display = `${minutes}: ${remainderSeconds}`;
displayTimer.textContent = display;
}
//this function gets the input from data, depending on session or break
function getDeadline() {
clearInterval(interval);
var time_input;
if (data.isSession) {
time_input = octopus.getCurrentSession();
} else {
time_input = octopus.getCurrentBreak();
}
data.isSession = !data.isSession;
startTimer(time_input * 60);
}
}
}
octopus.init();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!-- View 1: the settings contain session length and break length-->
<section>
<div id="session">
<button type="button" id="increaseSession">+</button>
<span id="displaySession">25</span>
<button type="button" id="decreaseSession">-</button>
</div>
<div id="break">
<button type="button" id="increaseBreak">+</button>
<span id="displayBreak">5</span>
<button type="button" id="decreaseBreak">-</button>
</div>
<div><button id="start">START</button>
<button id="next">NEXT</button>
<button id="stop">STOP</button></div>
</section>
<!--View 2: the timer itself-->
<div id="countdown-timer"></div>
I create plugin something like this
timer plugin
(function($) {
$.fn.timer = function(options) {
var defaults = {
seconds: 60
};
var options = $.extend(defaults, options);
return this.each(function() {
var seconds = options.seconds;
var $this = $(this);
var timerIntval;
var Timer = {
setTimer : function() {
clearInterval(timerIntval);
if(seconds <= 0) {
alert("timeout");
}else {
timerIntval = setInterval(function(){
return Timer.getTimer();
}, 1000);
}
},
getTimer : function () {
if (seconds <= 0) {
$this.html("0");
} else {
seconds--;
$this.html(seconds);
}
}
}
Timer.setTimer();
});
};
})(jQuery);
and I call the plugin like this.
$(".myTimer").timer({
seconds : 100
});
i called the plugin at timerpage.php. When i changed the page to xxx.php by clicking another menu, the timer interval is still running and i need to the clear the timer interval.
i created a webpage using jquery ajax load. so my page was not refreshing when i change to another menu.
my question is, how to clear the timer interval or destroy the plugin when i click another menu?
Please try with following modifications:
timer plugin:
(function($) {
$.fn.timer = function(options) {
var defaults = {
seconds: 60
};
var options = $.extend(defaults, options);
return this.each(function() {
var seconds = options.seconds;
var $this = $(this);
var timerIntval;
var Timer = {
setTimer : function() {
clearInterval(timerIntval);
if(seconds <= 0) {
alert("timeout");
}else {
timerIntval = setInterval(function(){
return Timer.setTimer();
}, 1000);
$this.data("timerIntvalReference", timerIntval); //saving the timer reference for future use
}
},
getTimer : function () {
if (seconds <= 0) {
$this.html("0");
} else {
seconds--;
$this.html(seconds);
}
}
}
Timer.setTimer();
});
};
})(jQuery);
Now in some other JS code which is going to change the div content
var intervalRef = $(".myTimer").data("timerIntvalReference"); //grab the interval reference
clearInterval(intervalRef); //clear the old interval reference
//code to change the div content on menu change
For clearing timer associated with multiple DOM element, you may check below code:
//iterate ovel all timer element:
$("h3[class^=timer]").each(function(){
var intervalRef = $(this).data("timerIntvalReference"); //grab the interval reference
clearInterval(intervalRef);
});
Hope this will give an idea to deal with this situation.
Instead of var timerIntval; set the variable timerInterval on the window object, then you will have the access this variable until the next refresh.
window.timerIntval = setInterval(function() {
Then when the user clicks on any item menu you can clear it:
$('menu a').click(function() {
clearInterval(window.timerIntval);
});
Live example (with multiple intervals)
$('menu a').click(function(e) {
e.preventDefault();
console.log(window.intervals);
for (var i = 0; i < window.intervals.length; i++) {
clearInterval(window.intervals[i]);
}
});
(function($) {
$.fn.timer = function(options) {
var defaults = {
seconds: 60
};
var options = $.extend(defaults, options);
return this.each(function() {
if (!window.intervals) {
window.intervals = [];
}
var intervalId = -1;
var seconds = options.seconds;
var $this = $(this);
var Timer = {
setTimer : function() {
clearInterval(intervalId);
if(seconds <= 0) {
alert("timeout");
} else {
intervalId = setInterval(function(){
//Timer.getTimer();
return Timer.getTimer();
}, 1000);
window.intervals.push(intervalId);
}
},
getTimer : function () {
if (seconds <= 0) {
$this.html("0");
} else {
seconds--;
$this.html(seconds);
}
}
}
Timer.setTimer();
});
};
})(jQuery);
$(".myTimer").timer({
seconds : 100
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<menu>
Menu 1
</menu>
<div class="myTimer"></div>
<div class="myTimer"></div>
Just notice that it's little bit risky because you can only run it once otherwise the interval id of the second will override the first.
I want to show countdown in each of my divs. Right now I get the seconds from my database and store in in data-countdown attribute and then use the following js code for countdown. Only the first div changes the value every second and the other ones do not change.
Here is the fiddle:
http://fiddle.jshell.net/j61qs7oc/
//imagine this line of code in every loop of a for loop so $remaining will be different
<div style="font-size: 25px; color:#e3b40b ; font-weight: 600;" data-countdown="'.$remaining.'"><i class="fa fa-spinner fa-pulse"></i></div>
here is the js code
$('[data-countdown]').each(function() {
finalDate = $(this).data('countdown');
var $this = $(this);
timeout = null;
time = null;
startCountdown($this,finalDate, 1000, end);
function startCountdown(display,timen, pause, callback) {
time = timen;
display.html(timen);
if (timen == 0)
callback();
else {
clearTimeout(timeout);
timeout = setTimeout(function () {
startCountdown(display,timen - 1, pause, callback)
}, pause);
}
}
function end() {
alert();
}
});
When you declare a variable without using the var keyword, you're creating a global. So each instance of your countdown is overwriting the previous value of finalDate, timeout, and time. Try adding var before each of those lines and it should do what you need. i.e.:
var finalDate = $(this).data('countdown');
var $this = $(this);
var timeout = null;
var time = null;
startCountdown($this,finalDate, 1000, end);
You could also just do this in regular Javascript instead of jQuery...
var countdownDivs = document.querySelectorAll('div[data-countdown]');
function end() {
alert();
}
function countdown(display, timen, pause, callback) {
display.innerHTML = timen;
if (timen == 0) callback();
else {
display.timeout;
clearTimeout(display.timeout);
display.timeout = setTimeout(function () {
countdown(display, timen - 1, pause, callback)
}, pause);
}
}
for(var i = countdownDivs.length>>>0; i--;){
countdown(countdownDivs[i], countdownDivs[i].dataset.countdown, 1000, end);
}
div[data-countdown]{
font-size:25px;
color:#e3b40b;
font-weight:600;
}
<div data-countdown="12312312"></div>
<div data-countdown="555555"></div>
<div data-countdown="95695"></div>
I'm trying to clear an interval when the user hovers over an element and then start it up again when they hover off an element. I think this is a closure but I'm not sure, hopefully my code will make sense what I'm trying to do.
var rotatorInterval = function(elem){
var interval = setInterval(function(){
var active = elem.find('.dot.active');
if(active.is('.dot:last-of-type',elem)){
elem.find('.dot').first().click();
}else{
active.next().click();
}
},6000);
interval;
return interval;
};
if($('.rotator').length){
$('.rotator').each(function(){
var self = $(this);
rotatorInterval(self);
self.find('.slide, .dot').on('mouseenter',function(){
console.log('hovered');
clearInterval(interval);
});
});
}
I tried returning the interval from that closure but when I hovered it said interval (the name of the variable I returned) is not defined, so it's like it didn't return it or something.
You just have to actually return the interval reference somewhere
var rotatorInterval = function (elem) {
var interval = setInterval(function () {
var active = elem.find('.dot.active');
if (active.is('.dot:last-of-type', elem)) {
elem.find('.dot').first().click();
} else {
active.next().click();
}
}, 6000);
return interval;
};
if ($('.rotator').length) {
$('.rotator').each(function () {
var self = $(this);
var return_interval = rotatorInterval(self);
self.find('.slide, .dot').on('mouseenter', function () {
clearInterval(return_interval);
});
});
}
Let's say I have the following divs:
<div class="a">You are funny.</div>
<div class="b">You are smart.</div>
<div class="c">You are cool.</div>
What is the best way to show div.a for 5 seconds, then fade out and fade in div.b for 5 seconds and then to div.c and then back to div.a and continue looping for an infinite amount of time?
Thanks :)
You can use an array of values and loop thru them.
var div = $('div').hide(),
news = ['news1', 'news2', 'news3'],
count = 0;
function changeNews() {
div.fadeIn().delay(500).fadeOut(function() {
changeNews();
}).text(news[count++])
if (count == news.length) {
count = 0;
}
}
changeNews();
Check working example at http://jsfiddle.net/qJa3h/
How about a jQuery plugin? I haven't tested it but something like this should get you started:
(function($) {
$.fn.keepFadingToNext = function(options) {
var defaults = {
duration: 5000
};
var settings = $.extend({}, defaults, options);
var original = this;
var doAnimations = function(element) {
element.fadeIn(settings.duration, function() {
element.fadeOut(settings.duration, function() {
var next = element.next();
if (next.length === 0) {
next = original;
}
doAnimations(next);
});
});
};
doAnimations(original);
return original;
};
})(jQuery)
$('.a').keepFadingToNext();
// or
$('.a').keepFadingToNext({ duration: 3000 });