Things to do:
settings form
is capturing the timer settings (count
, duration
, enable break
and break duration
) count
, duration
and break duration
are of type numberenable break
shows/hides the input for break duration
enable break
is set to false and disabled when count
is equal to 1
status panel
is showing: time elapsed
in the current intervalintervals done
count of intervals
controls buttons
to start
, pause
and stop
the timer start
button disables the settings form
, starts the timer and updates the status panel
pause
button pauses the timer… (logic so far :D)stop
button resets the timer to the values set in the settings form
and updates the status panel
This tutorial will cover:
'123'
into 123
and apply minimal validationMath.min()
/ Math.max()
All these parts will be described first to allow you to give a try and implement your own version.
How to style an application is not on the scope of this tutorial but the part of CSS used are explained. For more information about CSS grid or flexbox have a look at these fantastic articles:
How to execute JS code when the page is loaded
window.addEventListener('load', () => {
// code executed when the page is loaded
});
How to write an Immediately Invoked Function Expression (IIFE)
(() => {
// code executed automatically
})();
Read more on this article.
How to write a ternary expression
A ternary expression is written as condition ? when true : when false
.
const color = timer.isFinished ? 'green' : 'red';
How to add an event listener for button clicks, field input, etc
<button id="theButton">Start</button>
<input id="theInput"/>
document.getElementById('theButton').addEventListener('click', () => {
// code executed when the user clicks on the button
});
document.getElementById('theInput').addEventListener('input', () => {
// code executed when the user types in the input
});
How to execute a function at a regular interval
setInterval(() => {
// code executed at intervals of at least 1 second
}, 1000); // note: these are milliseconds (1s/1000)
The function setInterval
does not give an accurate interval, the code is executed at intervals of at least 1 second. It means it will usually takes longer than 1s before the function is called. Improving the accuracy is not be addressed in the tutorial but feel free to ask one of the web mentors.
Read the MDN official documentation about setInterval and clearInterval.
How to safely set text inside an element
<div id="theElement"></div>
document.getElementById('theElement').textContent = 'The text content';
Setting text using the textContent
property is safe and prevents potential Cross-site scripting (XSS) attacks.
Read the MDN official documentation about textContent and innerHTML security risk.
How to convert a number in a string into a number e.g. '123'
into 123
and apply minimal validation
parseInt('123'); // 123 as number
parseInt('aaa'); // NaN Not A Number (invalid user input)
// NaN can be tested with isNaN()
isNaN(parseInt('123')); // false
isNaN(parseInt('aaa')); // true
How to get the smallest / biggest value in a set of numbers with Math.min()
/ Math.max()
While very simple, these functions are great to reduce the clutter and make your code more readable.
const biggest = Math.max(-1, 1, 30, 2); // 30
const smallest = Math.min(-1, 1, 30, 2); // -1
Combined together, it allows to bind a value between a min and a max in one line, that rocks! \m/ (>_<) \m/
const boundBetween5and15 = Math.max(5, Math.min(aNumber, 15))
Don’t hesitate to use the console and break this expression step by step.
Read the MDN official documentation about Math.min and Math.max.
The whole application is implemented using 3 files to keep the demo simpler:
index.css
index.html
interval-timer-app.js
However, feel free to name them differently but don’t forget to update the path.
Start by creating a folder with:
index.css
html,
body {
height: 100%;
width: 100%;
box-sizing: border-box;
margin: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 1rem;
padding: 1rem;
background-color: #023;
color: #E4E5E6;
overflow: auto;
}
index.html
The HTML will load the stylesheet and the JS file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./index.css">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Interval timer - Coderdojo Athlone</title>
</head>
<body>
<!-- App markup goes here -->
<script src="./interval-timer-app.js"></script>
</body>
</html>
interval-timer-app.js
Note the use of IIFE (see above), not mandatory but good practice
(() => {
window.addEventListener('load', onLoad);
function onLoad() {
// code executed when the page is loaded
}
<!-- App JS code goes here -->
})();
Let’s first implement a basic version of the UI, with basic styles. The purpose is to have a clear idea of what needs to be done when implementing the JS logic.
Note: all the markup goes in the index.html
under <!-- App markup goes here -->
.
All elements referenced by JS have an id
attribute. ID are not the best ways to access the elements for larger apps. ID must be unique and they cause issues with reusability and scalability (things we don’t worry about in this tutorial).
The input type is set to "number"
to only allow numbers related characters e.g. 1
, 2
, 3
, -
, etc.
Note: this does not prevent the user to enter an invalid number. 123
, -123
and -1-2-3
are all accepted.
<div class="settings-form">
<div class="form-line">
<label for="intervalCountInput">Interval count:</label>
<input type="number" id="intervalCountInput" min="1">
</div>
<div class="form-line">
<label for="intervalDurationInput">Interval duration:</label>
<input type="number" id="intervalDurationInput" min="1">
</div>
<div class="form-line">
<label for="enableBreakInput">Enable break:</label>
<input type="checkbox" id="enableBreakInput">
</div>
<div class="form-line" id="breakDurationInputLine">
<label for="breakDurationInput">Break duration:</label>
<input type="number" id="breakDurationInput" min="1">
</div>
</div>
.form-line {
display: flex;
align-items: baseline;
margin-bottom: 1rem;
}
.form-line:last-of-type {
margin: 0;
}
.form-line label {
flex-grow: 1;
padding-right: .5rem;
}
.form-line input[type="number"] {
width: 60px;
text-align: right;
font-size: 1rem;
}
<div class="timer-controls">
<button id="startBtn">Start</button>
<button id="pauseBtn">Pause</button>
<button id="stopBtn">Stop</button>
</div>
.timer-controls {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
box-sizing: border-box;
padding: 2rem;
background-color: #356;
}
.timer-controls button {
font-size: 1rem;
margin: 0 1rem;
}
#startBtn {
color: #1f9136;
font-weight: bold;
}
#stopBtn {
color: #990000;
}
The status panel is split in two different areas, timer-overview
will display the time elapsed
in the current interval and timer-additional-info
some secondary information.
<div class="timer-status">
<div class="timer-overview">
<div id="timeOverviewMessage">Finish!</div>
<div class="box" id="elapsedInIntervalBox">
<header> </header>
<span id="elapsedInInterval"></span>s
</div>
<div class="box" id="elapsedInBreakIntervalBox">
<header>Break time</header>
<span id="elapsedInBreakInterval"></span>s
</div>
</div>
<div class="timer-additional-info">
<!-- Information about interval -->
<div class="box left-align">
<header>Intervals</header>
<div id="intervals"></div>
</div>
<div class="box">
<header>Intervals done</header>
<div id="intervalsDone"></div>
</div>
<div class="box">
<header>Intervals remaining</header>
<div id="intervalsRemaining"></div>
</div>
<!-- Information about time -->
<div class="box left-align">
<header>Total time</header>
<span id="totalTime"></span>s
</div>
<div class="box">
<header>Total time elapsed</header>
<span id="totalTimeElapsed"></span>s
</div>
<div class="box">
<header>Total time remaining</header>
<span id="totalTimeRemaining"></span>s
</div>
</div>
</div>
.timer-status header {
color: #888;
margin-bottom: .5rem;
font-size: 1rem;
}
.timer-status .box.left-align {
text-align: left;
}
.timer-overview .box {
flex: 0 0 50%;
margin-bottom: 1rem;
}
.timer-overview {
display: flex;
align-items: flex-end;
justify-content: center;
}
.timer-overview .box {
text-align: center;
font-size: 1.5rem;
}
#timeOverviewMessage,
#elapsedInInterval,
#elapsedInBreakInterval {
font-size: 3rem;
}
#timeOverviewMessage,
#elapsedInBreakIntervalBox {
display: none;
}
#timeOverviewMessage {
text-align: center;
flex: 1;
margin-bottom: 1rem;
}
.timer-additional-info {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
margin: 0 -1rem;
}
.timer-additional-info .box {
text-align: right;
flex: 1 0 33%;
padding: 1rem;
margin-bottom: .5rem;
box-sizing: border-box;
}
So far you should have the main components of the applications, let’s improve the layout a bit. Place all the components directly inside a div
with class="main-container"
:
<div class="main-container">
<div class="timer-status">...</div>
<div class="timer-controls">...</div>
<div class="settings-form">...</div>
</div>
Note: the order is not relevant.
We will use the CSS grid
to apply a simple layout. Have a look at the following rules in the link in the “Key parts” section at the start and try to set you own layout:
grid-gap
applies a gap between the component on the inner sidegrid-template-columns
declares the grid columns and their sizegrid-template-areas
declares the areas arrangement, grouped by rowsgrid-area
declares an area, must be used inside the selector for the specified area.main-container {
display: grid;
grid-gap: 2rem;
grid-template-areas: "status" "controls" "settings";
margin-bottom: 1rem;
}
.timer-status {
grid-area: status;
}
.timer-controls {
grid-area: controls;
}
.settings-form {
grid-area: settings;
}
The block above will stack the components as 3 rows with a gap of 2rem in between. This layout is specially suitable for phones where the screen is narrow.
For larger screens, you need to add CSS media queries (@media
) to define when to rearrange the layout.
@media only screen and (min-width: 768px) {
.main-container {
grid-template-columns: repeat(2, calc(50% - 2rem / 2));
grid-template-areas: "status status" "settings controls";
}
}
The block above will set 2 columns, and the width. The expression for the width may seem complicated but let’s explain it in details:
calc()
is a CSS utility to perform calculations e.g. 100% - 10px
, very powerful!50%
the width of one column as there are 22rem / 2
is the grid gap value divided between the two columns.1rem
(but wanted to remind to take in account the gap in the measure of the cell)Finally, add max-width
and margin
to the previous block in order to center the grid and set a maximum width.
.main-container {
max-width: 960px;
margin: 0 auto;
}
Implementing the logic can be challenging when many parts are involved. The following sections cover how to implement each part progressively in understandable chunks.
Note: all the JS code goes in the interval-timer-app.js
.
One of the simplest things (and probably the best) to start with is your data. You need to think what needs to be stored and what can be re-calculated.
Example:
On the list above, one of the information is redundant, it can be calculated from the 2 others.
The data stored in the timer settings and the current timer will be set as follow:
timer:
totalTimeElapsed
: {number}elapsedInInterval
: {number}intervalsDone
: {number}isBreak
: {boolean}isFinished
: {boolean}Arguably, we could also remove isFinished
because we can calculate its value with totalTimeElapsed
and total time
.
timerSettings:
intervalCount
: {number}intervalDuration
: {number}enableBreak
: {boolean}breakDuration
: {number}The data will be stored into objects accessible anywhere in the scope of our app:
Create a function to set the timer
default values.
timerSettings
, it should set all properties with the passed arguments. In order for this function to be more convenient to use, it should set the default value when an argument is missing. Javascript has a very convenient way to declare a default value.onLoad()
to call the functions defined above to initialize the default values.Add the following to interval-timer-app.js
:
let
timer,
timerSettings;
function resetTimer() {
timer = {
totalTimeElapsed: 0,
elapsedInInterval: 0,
intervalsDone: 0,
isBreak: false,
isFinished: false
};
}
function setTimerSettings(
intervalCount = timerSettings.intervalCount, // declare argument with default value if undefined
intervalDuration = timerSettings.intervalDuration,
enableBreak = timerSettings.enableBreak,
breakDuration = timerSettings.breakDuration
) {
timerSettings = {
intervalCount,
intervalDuration,
enableBreak,
breakDuration
};
}
function onLoad() {
setTimerSettings(5, 10, true, 5);
resetTimer();
// ...
}
// rest of the code
Read more about the syntax used above: property shorthand, default parameter values.
The application accesses the elements based on their ID using document.getElementById('theId')
. Remember ID must be unique.
The element references will be stored into objects accessible anywhere in the scope of our app:
settings form
.controls buttons
.status panel
.onLoad()
to call the functions defined above to initialize the reference objects.let
formSettingsFields,
timerControlsButtons,
statusPanel;
function initializeTimerSettingsForm() {
formSettingsFields = {
intervalCount: document.getElementById('intervalCountInput'),
intervalDuration: document.getElementById('intervalDurationInput'),
enableBreak: document.getElementById('enableBreakInput'),
breakDuration: document.getElementById('breakDurationInput'),
};
}
function initializeTimerControls() {
timerControlsButtons = {
start: document.getElementById('startBtn'),
pause: document.getElementById('pauseBtn'),
stop: document.getElementById('stopBtn')
};
}
function initializeStatusPanel() {
statusPanel = {
timeOverviewMessage: document.getElementById('timeOverviewMessage'),
elapsedInIntervalBox: document.getElementById('elapsedInIntervalBox'),
elapsedInBreakIntervalBox: document.getElementById('elapsedInBreakIntervalBox'),
elapsedInInterval: document.getElementById('elapsedInInterval'),
elapsedInBreakInterval: document.getElementById('elapsedInBreakInterval'),
intervalsDone: document.getElementById('intervalsDone'),
intervalsRemaining: document.getElementById('intervalsRemaining'),
intervals: document.getElementById('intervals'),
totalTimeElapsed: document.getElementById('totalTimeElapsed'),
totalTimeRemaining: document.getElementById('totalTimeRemaining'),
totalTime: document.getElementById('totalTime'),
};
}
function onLoad() {
// ...
initializeTimerSettingsForm();
initializeTimerControls();
initializeStatusPanel();
}
An event, in the term of JS, is when something occurs on the page. e.g. user interaction click
, scroll
, etc.
An event handler is the function which is be called in response to an event.
Remember: the reference to the element must be set before calling addEventListener
e.g. call addEventListener
after initializing the reference object.
The logic of the event handler will be implemented further down. The goal is to make sure the events are properly triggered, use a console.log()
or a breakpoint to check.
initializeTimerControls
to add event handlers for start
, pause
and stop
.function initializeTimerControls() {
// ...
timerControlsButtons.start.addEventListener('click', startTimer);
timerControlsButtons.pause.addEventListener('click', pauseTimer);
timerControlsButtons.stop.addEventListener('click', stopTimer);
}
function startTimer() {
console.log('start button clicked');
}
function pauseTimer() {
console.log('pause button clicked');
}
function stopTimer() {
console.log('stop button clicked');
}
initializeTimerSettingsForm
to add the event handlers in order to capture when the settings are updated: input
to detect a change of the value in the input field.change
to detect a change of the value in the checkbox field.Note: Try to replace the input
event by change
, keydown
, keyup
or keypress
and notice the difference.
function initializeTimerSettingsForm() {
// ...
formSettingsFields.intervalCount.addEventListener('input', () => {
console.log('intervalCount field updated', formSettingsFields.intervalCount.value);
});
formSettingsFields.intervalDuration.addEventListener('input', () => {
console.log('intervalDuration field updated', formSettingsFields.intervalDuration.value);
});
formSettingsFields.enableBreak.addEventListener('change', () => {
console.log('enableBreak checkbox updated', formSettingsFields.enableBreak.checked);
});
formSettingsFields.breakDuration.addEventListener('input', () => {
console.log('breakDuration field updated', formSettingsFields.breakDuration.value);
});
}
Before implementing the logic for the event handlers, you should plan what will be required in those. One of the things required is to change the disabled state. You should create functions in order to avoid code duplication every time you need to change the disabled state of all fields or controls.
Control’s disabled state needs to be updated independently.
Create a new function setTimerControlsDisabledState
to manage the control’s disabled state.
function setTimerControlsDisabledState(start, pause, stop) {
timerControlsButtons.start.disabled = start;
timerControlsButtons.pause.disabled = pause;
timerControlsButtons.stop.disabled = stop;
}
Form field’s disabled state will always be the same value except for enableBreak
. The field enableBreak
needs to stay disabled when timerSettings.intervalCount
equals 1.
Create a new function setFormDisabledState
to set all field’s disabled state.
function setFormDisabledState(disabled) {
formSettingsFields.intervalCount.disabled = disabled;
formSettingsFields.intervalDuration.disabled = disabled;
formSettingsFields.enableBreak.disabled = disabled || timerSettings.intervalCount === 1;
formSettingsFields.breakDuration.disabled = disabled;
}
The purpose of this section is to implement the ticking scheduling part only. The full timer ticking logic is more complex and is addressed separately further down.
Create three functions:
startTimerTick
starts the timer ticking using setInterval
and onTimerTick
and keep the intervalID
for later cancelling.stopTimerTick
cancel the interval calling onTimerTick
.onTimerTick
function being called by setInterval
, this is where the timer’s logic will be implemented.function startTimerTick() {
timer.intervalId = setInterval(onTimerTick, 1000);
}
function stopTimerTick() {
clearInterval(timer.intervalId);
}
function onTimerTick() {
console.log('tick!');
}
In this section you should start to put together different parts implemented until now.
The controls event handlers should be updated to prevent unexpected state.
startTimer
function: startTimerTick
.resetTimer
when the timer is marked as isFinished
.settings form
.start
button and enable pause
and stop
.pauseTimer
function: stopTimerTick
.start
button and disable pause
(stop
remains enabled).stopTimer
function: stopTimerTick
.resetTimer
.settings form
.start
button and disable pause
and stop
.function startTimer() {
setFormDisabledState(true);
setTimerControlsDisabledState(true, false, false);
if (timer.isFinished) {
resetTimer();
}
startTimerTick();
}
function pauseTimer() {
setTimerControlsDisabledState(false, true, false);
stopTimerTick();
}
function stopTimer() {
setFormDisabledState(false);
setTimerControlsDisabledState(false, true, true);
stopTimerTick();
resetTimer();
}
You should now be able to start and pause/stop the ticking.
The “Key parts” section explains how to convert a string to number, how to detect invalid numbers with isNaN
and how to apply min / max bounds. Implementing the settings form
event handlers will require a minimum of data validation.
999999999999999999999999999999
intervals :DCreate a new function getNumberInBoundsOrDefault
which takes the value to validate with its minimum, maximum and default (when invalid).
function getNumberInBoundsOrDefault(value, min, max, def = 1) {
const valueAsNumber = parseInt(value);
return isNaN(valueAsNumber) ? def : Math.max(min, Math.min(valueAsNumber, max));
}
The function above will be used to validate data entered for intervalCount
, intervalDuration
, breakDuration
.
Create a new function setBreakDurationLineDisplay
which takes a boolean value. It should apply/clear the display
to breakDurationInputLine
.
function setBreakDurationLineDisplay(displayed) {
const breakDurationInputLineElt = document.getElementById('breakDurationInputLine');
breakDurationInputLineElt.style.display = displayed ? null : 'none';
}
Update the event handlers in initializeTimerSettingsForm
to:
setTimerSettings
with the updated value after validation.#breakDurationInputLine
based on enableBreak
value.#breakDurationInputLine
based on intervalCount
value (no break when value equals 1).Note: lastUserSetEnableBreak
is used to keep track of the last value set by the user in order to restore it when intervalCount
is not equal to 1.
function initializeTimerSettingsForm() {
const oneDayInSeconds = 60 * 60 * 24;
let lastUserSetEnableBreak = timerSettings.enableBreak;
// ...
formSettingsFields.intervalCount.addEventListener('input', () => {
const
intervalCount = getNumberInBoundsOrDefault(formSettingsFields.intervalCount.value, 1, 999),
hasOneInterval = intervalCount === 1,
hasBreak = hasOneInterval ? false : lastUserSetEnableBreak;
formSettingsFields.enableBreak.disabled = hasOneInterval === true;
formSettingsFields.enableBreak.checked = hasBreak;
setBreakDurationLineDisplay(hasBreak);
setTimerSettings(intervalCount, undefined, hasBreak);
});
formSettingsFields.intervalDuration.addEventListener('input', () => {
setTimerSettings(undefined, getNumberInBoundsOrDefault(formSettingsFields.intervalDuration.value, 1, oneDayInSeconds));
});
formSettingsFields.enableBreak.addEventListener('change', () => {
const enableBreak = formSettingsFields.enableBreak.checked;
lastUserSetEnableBreak = enableBreak;
setBreakDurationLineDisplay(enableBreak);
setTimerSettings(undefined, undefined, enableBreak);
});
formSettingsFields.breakDuration.addEventListener('input', () => {
setTimerSettings(undefined, undefined, undefined, getNumberInBoundsOrDefault(formSettingsFields.breakDuration.value, 1, oneDayInSeconds));
});
}
The value undefined
is used above to prevent repeating the matching timerSettings
values. Undefined values will be replaced with the defaults defined in the setTimerSettings
function.
Update the function initializeTimerSettingsForm
to set the initial values of timerSettings
.
function initializeTimerSettingsForm() {
// ...
formSettingsFields.intervalCount.value = timerSettings.intervalCount;
formSettingsFields.intervalDuration.value = timerSettings.intervalDuration;
formSettingsFields.enableBreak.checked = timerSettings.enableBreak;
formSettingsFields.breakDuration.value = timerSettings.breakDuration;
// ...
}
Remember: the reference to the element must be set before setting the value e.g. set the value after initializing the reference object.
In this section, you will implement the part to display the information about the timer.
Note: both elements #timeOverviewMessage
and #elapsedInBreakIntervalBox
have display: none;
to hide them by default.
Create a new function updateInfo
to manage the display of the information:
#elapsedInIntervalBox
when on NOT on break, hide when on break.#elapsedInBreakIntervalBox
when on break, hide when not.#timeOverviewMessage
when timer is finished, hide until then.timer.elapsedInInterval
in #elapsedInIntervalBox
or #elapsedInBreakIntervalBox
based on “on break” value.intervalsDone
intervalsRemaining
intervals
totalTimeElapsed
totalTimeRemaining
totalTime
You will need to compute totalTime
and totalTimeRemaining
.
intervalCount * intervalDuration
breakDuration
for breaks.intervalCount
set to different values, but make sure to test with 1
.This function will require you to change the value of timer
few times to cover all possibilities. Ideally, it should be done as part of a test script, we should cover how in a more advanced tutorial.
It is tempting to implement onTimerTick
and then updateInfo
but debugging would be harder. Debugging is easier when the debugged part is not impacted by too many outside factors.
function updateInfo() {
statusPanel.timeOverviewMessage.style.display = timer.isFinished ? 'block' : null;
statusPanel.elapsedInIntervalBox.style.display = timer.isFinished || timer.isBreak ? 'none' : null;
statusPanel.elapsedInBreakIntervalBox.style.display = !timer.isFinished && timer.isBreak ? 'block' : null;
if (timer.isBreak) {
statusPanel.elapsedInBreakInterval.textContent = timer.elapsedInInterval;
} else {
statusPanel.elapsedInInterval.textContent = timer.elapsedInInterval;
}
const {
intervalCount, intervalDuration, enableBreak, breakDuration
} = timerSettings,
totalTime = (intervalCount * intervalDuration) + (enableBreak ? Math.max(intervalCount - 1, 1) * breakDuration : 0);
statusPanel.intervalsDone.textContent = timer.intervalsDone;
statusPanel.intervalsRemaining.textContent = timerSettings.intervalCount - timer.intervalsDone;
statusPanel.intervals.textContent = timerSettings.intervalCount;
statusPanel.totalTimeElapsed.textContent = timer.totalTimeElapsed;
statusPanel.totalTimeRemaining.textContent = totalTime - timer.totalTimeElapsed;
statusPanel.totalTime.textContent = totalTime;
}
Read more about the syntax used above: destructuring assignment.
Update now all event handlers of the settings form to call updateInfo
after they update the timer settings.
The function onTimerTick
probably hold the most complex part of the application. Let’s break the logic into plain english first:
timerSettings.enableBreak
and timer.isBreak
timerSettings.intervalDuration
or timerSettings.breakDuration
based on timer.isBreak
valuetimer.totalTimeElapsed
should be incrementedtimer.elapsedInInterval
timer.intervalsDone
timer.elapsedInInterval
when the interval is overtimer.isBreak
when the interval is over and if timerSettings.enableBreak
is true
timer.isFinished
timer.isFinished
is true
pause
and stop
buttonsstopTimerTick
updateInfo
function onTimerTick() {
const
isBreak = timerSettings.enableBreak && timer.isBreak,
currentIntervalDuration = isBreak ? timerSettings.breakDuration : timerSettings.intervalDuration;
if (timer.elapsedInInterval < currentIntervalDuration) {
timer.elapsedInInterval++;
} else {
if (!timer.isBreak) {
timer.intervalsDone++;
}
timer.isBreak = timerSettings.enableBreak ? !timer.isBreak : false;
timer.isFinished = timer.intervalsDone === timerSettings.intervalCount;
if (!timer.isFinished) {
timer.elapsedInInterval = 1;
}
}
if (timer.isFinished) {
setTimerControlsDisabledState(false, true, true);
setFormDisabledState(false);
stopTimerTick();
} else {
timer.totalTimeElapsed++;
}
updateInfo();
}
You did it! or just scrolled up to here ;)
Hopefully this tutorial helped you understand some of the basic concepts e.g. event handlers, the Math API, intervals etc but also how to break a problem into small digestible chunks.
We welcome feedback on how we could improve this tutorial so feel free to share your thoughts with one of the web mentors.
Keep on learning!
See the full example with code sandbox in CodePen.
See the Pen (Tutorial) Interval timer by Marc Ed Raffalli (@marc-ed-raffalli) on CodePen.