Reputation: 124
This is my first time using a while loop in a practical application so please forgive my ignorance.
I am creating a webpage that demonstrates the costs of using a lightbulb over time.
At this stage I am trying to use a while loop to update and display the number of hours that have passed since the user clicked the light switch. (1 hour represents 1 second of realtime)
When I set breakpoints on firebug, everything operates normally until I get to the setTimeout method within my while loop. After it breaks at the setTimeout method and I click continue, it immediately breaks at the same place again, without actually executing anything else.
When I don't set breakpoints, it freezes firefox and I have to stop script execution.
I rechecked to make sure that I am using setTimeout properly. Now I'm not even sure where to check or what to search for because I don't understand whats going wrong. Even just a hint of what I might check or research would be immensely helpful.
I have tried to comment the code as verbosely as possible. I'll be happy to clarify something if needed.
I would highly recommend taking a look at the jsfiddle:
But here is my code:
My JS
$(document).ready(function () {
//set image to default off position
$('#lightswitch').css("background-image", "url(http://www.austinlowery.com/graphics/offswitch.png)");
// setup the lifetime hours of the lightbulb for later use
var lifetimeHours = 0;
// setup function to update the calculated lifetime hours number on the webpage to
// be called later
function updateLifetimeHoursHtml (lifetimeHours) {
$('#lifetimeHours').html(lifetimeHours);
}
// set up function to to send to setTimeout
function updateNumbers () {
// increment lifetimeHours by one
lifetimeHours = lifetimeHours++;
// call function to update the webpage with the new number result
updateLifetimeHoursHtml(lifetimeHours);
}
// When the lightswitch on the webpage is clicked, the user should see the
// lifetime hours update every second until the user clicks the switch again
// which will then display the off graphic and pause the updating of the lifetime
// hours
$('#lightswitch').click(function(){
// if the lightswitch is off:
if ($('#lightswitch').attr('state') == 'off') {
// set switch to on
$('#lightswitch').attr('state', 'on');
// update graphic to reflect state change
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
// start updating the lifetime hours number on the webpage
// while the #lightswitch div is in the on state:
while ($('#lightswitch').attr('state') == 'on'){
//call update numbers every second
setTimeout('updateNumbers()', 1000);
}
// the lightswich was not in the off state so it must be on
}else{
// change the state of the switch to off
$('#lightswitch').attr('state', 'off');
// update graphic to reflect state change
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
};
});
});
My HTML
<div id="container">
<div id="lightswitch" state="off"> </div>
<span>After </span><span id="lifetimehours">0</span><span> lifetime hours:</span>
<br><br>
<span><b>You have spent:</b></span>
<br><br>
<span id="dollaramoutelectricity"></span><span> on electricty</span>
<br>
<span id="mainttime"></span><span> on maintenace</span>
<br>
<span id="dollaramountbulbs"></span><span> on replacement bulbs</span>
<br><br>
<span><b>You have:</b></span>
<br><br>
<span>Produced </span><span id="amountgreenhousegasses"></span><span> of greenhouse gasses</span>
<br>
<span>Sent </span><span id="amounttrash"></span><span> of trash to the dump</span>
<br>
<span>Used </span><span id="amountelectricty"></span><span> of electricity</span>
</div>
Upvotes: 0
Views: 2155
Reputation: 287990
Based on yngum's answer,
I think it's better like this:
if ($('#lightswitch').attr('state') == 'off') {
$('#lightswitch').attr('state', 'on');
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
updateNumbers();
}
And then
function updateNumbers () {
lifetimeHours++;
updateLifetimeHoursHtml(lifetimeHours);
if($('#lightswitch').attr('state') == 'on'){
setTimeout(updateNumbers,1000);
}
}
See it here: http://jsfiddle.net/tdS3A/24/
But if you want maximum precision, you should store new Date().getTime()
, because doing setTimeout
or setInterval
of 1 second doesn't ensure you that it will be called each second...
function updateNumbers () {
// call function to update the webpage with the new number result
updateLifetimeHoursHtml(lifetimeHours+(new Date().getTime()-timerStart)/1000);
// call updateNumbers() again
if($('#lightswitch').attr('state') == 'on'){
timer=setTimeout(updateNumbers,1000);
}
}
$('#lightswitch').click(function(){
// if the lightswitch is off:
if ($('#lightswitch').attr('state') == 'off') {
// set switch to on
$('#lightswitch').attr('state', 'on');
// update graphic to reflect state change
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/onswitch.png)");
// update the lifetime hours number on the webpage
timerStart=new Date().getTime();
timer=setTimeout(updateNumbers,1000);
// the lightswich was not in the off state so it must be on
}else{
// change the state of the switch to off
lifetimeHours+=(new Date().getTime()-timerStart)/1000;
clearTimeout(timer);
$('#lightswitch').attr('state', 'off');
// update graphic to reflect state change
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
};
});
See it here: http://jsfiddle.net/tdS3A/26/
Even if you want the number with maximum precision because you want the calculations to be precise, maybe you want to round that value when you show it to the user. Then, use
function updateLifetimeHoursHtml (lifetimeHours) {
$('#lifetimehours').html(Math.round(lifetimeHours));
}
Upvotes: 1
Reputation: 26753
var switchTimer;
$('#lightswitch').click(function(){
if ($('#lightswitch').attr('state') == 'off') {
$('#lightswitch').attr('state', 'on');
switchTimer = setInterval(updateNumbers, 1000);
} else {
$('#lightswitch').attr('state', 'off');
$('#lightswitch').css("background-image", "url(http://austinlowery.com/graphics/offswitch.png)");
clearInterval(switchTimer);
};
Javascript is an event based language. This means that codes doesn't run constantly. It only runs when there is an event. By using a while loop you have basically frozen it - the javascript is constantly running inside that loop. That's fine for languages like C, which have to have something running all the time.
But it's a mistake for javascript. For javascript you have to code it to respond to an event, then stop. The setInterval constantly generates an event for you, running a function every xx milliseconds.
In between runs of the timer no code is running! This is important to remember.
Upvotes: 3
Reputation: 5684
Since setTimeout is generally preferred over setInterval, another solution is
Change
while ($('#lightswitch').attr('state') == 'on'){
//call update numbers every second
setTimeout('updateNumbers()', 1000);
To
setTimeout(function() {
updateNumbers();
if ($('#lightswitch').attr('state') == 'on')
setTimeout(arguments.callee, 1000);
}, 0);
This will check ur lightswitch every second, and stop when it is off.
Upvotes: 0
Reputation: 144872
JavaScript uses an event loop. Because your while
loop never yields (#lightswitch
's state
will forever be on
because the infinite loop locks the UI, preventing the user from turing it off), the event loop never runs again, and the callback you register with setTimeout
never gets a chance to execute.
What you're really looking to do is update your counter with a setInterval
function.
setInterval(updateNumbers, 1000);
Upvotes: 4