Hello, I have a timer that I built from a Storyline provided in another post; it uses a javascript snippet to start the timer and works great however, I am trying to add a feature to stop the timer and/or reset it (both would be nice but either would be helpful). Can anyone provide some help?
Did you ever get a javascript timer working that can be stopped manually or reset? I'm trying to do the same thing, but all the JS timers here start a new process when the trigger to start is fired again. And then go crazy by displaying 2 or more timers at the same time.
Basically it is scope that makes most timers used onhere ( the one from Toni too ) tough to stop/pause and/or resume.
Scope means that if you define some javascript variable on some spot, it will not automatically be available in another spot. Eg. if you create a trigger on a slide with a function in it.... that function won't be available in any other slide. In fact it wont even be available in another trigger on that slide. It only is available in that particular trigger.
Thanks for your reply and link to article about scope and adding JS functions to package.
If I understand the issue with the timer correctly - you can only access the function that operates the timer in the same slide and same trigger that initialize it.
So - as you say to use SL variables which have global scope, can you not have a variable called "pause" which fires on any slide you want if triggered, and a reference to that variable in the JS function that started the timer? So that whenever the pause variable is set to true - the JS timer will pause (provided of course the function is correctly coded to stop counting when pause = true)
Yeah basically thats correct. The difficulty in working with Storyline variables and scope for a timer is that with only one variable for pause it won't work. Say you initialize your timer on the first slide, lets call it slide A with trigger tA... the scope for that timer is then exactly there. You can tell the global variable ( varT = true ) the timer is running...and thus..when you get to slide B you do know the timer is running... and you can change the global variable ( varT = off )...but you then donot have access to the scope of the timer...because that is on slide A with trigger tA.
So when using variables only you need to add the timer itself to a global variable, so you access it anywhere. In fact that is cumbersome because in the end it takes quite a few variables for it to work. I prefer a global script on root level... i will make a sample when time permits this week.
Doesnt the JS function just kind of keep on running once started, and then use the latest value of SL global variable when the getplayer function is called in the counter function? So that if pause=true, set on any slide, on its next cycle to add 1 second to the counter time, the js function will use pause=true and thus pause?
The moment you leave a slide...all JS related things not set in global variables or Storyline variables ( those are global too ) are gone. Im gonna make some samples in Storyline to share to explain the scope thing better. That way its a lot clearer.
The sample you sent me works because it uses Date as a global object. The current Date and Time are always the same ( timezone dependant ofcourse ) and each slide compares and calculates timedifference with the start time.
I will make samples showing explaining scope and a global timer script solution. Only not this week ;-)
Christoph Krieger this is exact the same method Sam already uses. The Date Object in your code uses the global date to parse the time elapsed. Although this works fine it only shows the scope issue thats underlying to all triggers in Storyline. The SetInterval method in this code only works because it uses the global Date Object.
Sorry but I can not get the point. What is bad with the date object?
The original request was a timer which can be paused and reset. Never said mine is different to others. Sam just asked me in an email if I could provide the file I have created few years ago.
Yeah the solution is fine. Only thing is that Sam already had that solution a few years back as he mailed me ;-)
Only thing i dislike is adding code time after time in the index file when publishing. Using a WebObject to inject it into the html would be better. Or edit the index file in Articulate Programmes folder then you only have to do it once.
I think the original one I had was from Christoph actually, and he was kind enough to send here a version with separate script file. And thanks Toni for sending that here too.
It is also easily modified to countdown instead, so that's great.
After about 15 hrs of chatgpt helping me write code I've got something that works!
function zeros(num) { if (num < 10) { return "0" + num; } return num; }
var player = GetPlayer(); var count = 20; // Set the countdown duration to 20 seconds var timerID = player.GetVar("Timer_ID"); // Retrieve the stored timer ID
function startTimer() { timerID = setInterval(timer, 1000); player.SetVar("Timer_ID", timerID); // Store the timer ID in a Storyline variable }
function stopTimer() { clearInterval(timerID); }
function resetTimer() { stopTimer(); // Stop the previous timer count = 20; // Reset the countdown duration to 20 seconds var minutes = zeros(Math.floor(count / 60)); var seconds = zeros(count % 60); var totalTime = minutes + ':' + seconds; player.SetVar("Countdown_Display", totalTime); player.SetVar("Timer_finished", 0); startTimer(); // Start the new timer }
function timer() { if (player.GetVar("Reset_Timer") === 1) { resetTimer(); player.SetVar("Reset_Timer", 0); // Reset the player variable return; }
count = count - 1; var minutes = zeros(Math.floor(count / 60)); var seconds = zeros(count % 60);
var totalTime = minutes + ':' + seconds; player.SetVar("Countdown_Display", totalTime);
if (count <= 0) { player.SetVar("Timer_finished", 1); stopTimer(); // Stop the timer when countdown is finished } }
// Clear the previous interval timer if the stored timer ID is valid if (timerID) { clearInterval(timerID); }
// Initial setup resetTimer(); // Call the resetTimer() function initially to set up the countdown
// Example usage: Set the player variable "Reset_Timer" to 1 to reset the timer player.SetVar("Reset_Timer", 1);
17 Replies
Hi Toni,
Take a look at this timer.
Hi Toni
Did you ever get a javascript timer working that can be stopped manually or reset? I'm trying to do the same thing, but all the JS timers here start a new process when the trigger to start is fired again. And then go crazy by displaying 2 or more timers at the same time.
Basically it is scope that makes most timers used onhere ( the one from Toni too ) tough to stop/pause and/or resume.
Scope means that if you define some javascript variable on some spot, it will not automatically be available in another spot. Eg. if you create a trigger on a slide with a function in it.... that function won't be available in any other slide. In fact it wont even be available in another trigger on that slide. It only is available in that particular trigger.
To overcome that you have 2 possibilities.
- Use Storyline variables to save and pass Javascript vars. SL vars are in a global scope.
- Add your Javascript functions and code to the HTML. By using external JS files like my generic_functions setup you can create global JS code that can be used at any time any where.
( https://community.articulate.com/discussions/articulate-storyline/including-javascript-for-repeating-functionality )
Hi Math
Thanks for your reply and link to article about scope and adding JS functions to package.
If I understand the issue with the timer correctly - you can only access the function that operates the timer in the same slide and same trigger that initialize it.
So - as you say to use SL variables which have global scope, can you not have a variable called "pause" which fires on any slide you want if triggered, and a reference to that variable in the JS function that started the timer? So that whenever the pause variable is set to true - the JS timer will pause (provided of course the function is correctly coded to stop counting when pause = true)
Ditto for reset
Hi Sam,
Yeah basically thats correct. The difficulty in working with Storyline variables and scope for a timer is that with only one variable for pause it won't work. Say you initialize your timer on the first slide, lets call it slide A with trigger tA... the scope for that timer is then exactly there. You can tell the global variable ( varT = true ) the timer is running...and thus..when you get to slide B you do know the timer is running... and you can change the global variable ( varT = off )...but you then donot have access to the scope of the timer...because that is on slide A with trigger tA.
So when using variables only you need to add the timer itself to a global variable, so you access it anywhere. In fact that is cumbersome because in the end it takes quite a few variables for it to work. I prefer a global script on root level... i will make a sample when time permits this week.
Hi Math
Sorry to keep asking questions!
Doesnt the JS function just kind of keep on running once started, and then use the latest value of SL global variable when the getplayer function is called in the counter function? So that if pause=true, set on any slide, on its next cycle to add 1 second to the counter time, the js function will use pause=true and thus pause?
The moment you leave a slide...all JS related things not set in global variables or Storyline variables ( those are global too ) are gone. Im gonna make some samples in Storyline to share to explain the scope thing better. That way its a lot clearer.
The sample you sent me works because it uses Date as a global object. The current Date and Time are always the same ( timezone dependant ofcourse ) and each slide compares and calculates timedifference with the start time.
I will make samples showing explaining scope and a global timer script solution.
Only not this week ;-)
I have updated a javascript countdown/timer example I have created several years ago.
You can start, pause or reset the timer.
https://360.articulate.com/review/content/45c48644-63e2-427c-bf96-ab8f1d06360c/review
You just need to adjust the SL variable: timeINmilliseconds
Christoph Krieger this is exact the same method Sam already uses. The Date Object in your code uses the global date to parse the time elapsed. Although this works fine it only shows the scope issue thats underlying to all triggers in Storyline. The SetInterval method in this code only works because it uses the global Date Object.
Here is an quick and dirty example of global countdown made with javascript:
https://malziland.at/articulate/countdown_global_js/story.html
After publishing:
<script src="scripts.js"></script>
Code still the same...and SetInterval still depends on the Date Object. Works fine but not much difference with Sam's original.
Sorry but I can not get the point. What is bad with the date object?
The original request was a timer which can be paused and reset. Never said mine is different to others. Sam just asked me in an email if I could provide the file I have created few years ago.
Sam, here is what I ended up with. Hope it helps.
Yeah the solution is fine. Only thing is that Sam already had that solution a few years back as he mailed me ;-)
Only thing i dislike is adding code time after time in the index file when publishing. Using a WebObject to inject it into the html would be better. Or edit the index file in Articulate Programmes folder then you only have to do it once.
You are right with the webobjects.
I gave up using it for a while as there was a bug that I needed to rename the folder every time I changed a thing.
However! This is the great thing that Storyline offers many ways to solve the same problem.
Thanks Christoph and Toni :)
I think the original one I had was from Christoph actually, and he was kind enough to send here a version with separate script file. And thanks Toni for sending that here too.
It is also easily modified to countdown instead, so that's great.
After about 15 hrs of chatgpt helping me write code I've got something that works!
function zeros(num) {
if (num < 10) {
return "0" + num;
}
return num;
}
var player = GetPlayer();
var count = 20; // Set the countdown duration to 20 seconds
var timerID = player.GetVar("Timer_ID"); // Retrieve the stored timer ID
function startTimer() {
timerID = setInterval(timer, 1000);
player.SetVar("Timer_ID", timerID); // Store the timer ID in a Storyline variable
}
function stopTimer() {
clearInterval(timerID);
}
function resetTimer() {
stopTimer(); // Stop the previous timer
count = 20; // Reset the countdown duration to 20 seconds
var minutes = zeros(Math.floor(count / 60));
var seconds = zeros(count % 60);
var totalTime = minutes + ':' + seconds;
player.SetVar("Countdown_Display", totalTime);
player.SetVar("Timer_finished", 0);
startTimer(); // Start the new timer
}
function timer() {
if (player.GetVar("Reset_Timer") === 1) {
resetTimer();
player.SetVar("Reset_Timer", 0); // Reset the player variable
return;
}
count = count - 1;
var minutes = zeros(Math.floor(count / 60));
var seconds = zeros(count % 60);
var totalTime = minutes + ':' + seconds;
player.SetVar("Countdown_Display", totalTime);
if (count <= 0) {
player.SetVar("Timer_finished", 1);
stopTimer(); // Stop the timer when countdown is finished
}
}
// Clear the previous interval timer if the stored timer ID is valid
if (timerID) {
clearInterval(timerID);
}
// Initial setup
resetTimer(); // Call the resetTimer() function initially to set up the countdown
// Example usage: Set the player variable "Reset_Timer" to 1 to reset the timer
player.SetVar("Reset_Timer", 1);