complete-stopwatch
Demonstration of a state machine with data actions
by Tracy-Gregory Gilmore
HTML
<main>
<div class="stateDisplay" id="divState"></div>
<button id="btnStart">START</button>
<button id="btnPause">PAUSE</button>
<button id="btnStop">STOP</button>
<button id="btnClear">CLEAR</button>
</main>
CSS
.stateDisplay {
background-color: #DDD;
text-align: center;
text-transform: uppercase;
width: 7em;
font-size: 2rem;
margin: 2px;
padding: 0.25em;
font-family: monospace;
}
JavaScript
function showState(counter) {
const msec = `${counter % 100}`.padStart(2, '0');
const secs = `${Math.floor((counter % 6000) / 100)}`.padStart(2, '0');
const mins = `${Math.floor(counter / 6000)}`.padStart(2, '0');
divState.textContent = `${mins}:${secs}.${msec}`;
}
function enableButtons(stateEngine) {
btnStart.addEventListener('click', () => stateEngine.trigger('START'));
btnStop.addEventListener('click', () => stateEngine.trigger('STOP'));
btnPause.addEventListener('click', () => stateEngine.trigger('PAUSE'));
btnClear.addEventListener('click', () => stateEngine.trigger('CLEAR'));
return stateEngine;
}
function StateEngine(machine, initial, subject) {
subject.state = initial;
showState(subject.counter);
return {
trigger(event) {
const currentState = machine[subject.state];
if (event in currentState) {
subject.state = currentState[event].state;
if ('_action' in currentState[event]) {
currentState[event]._action();
}
if ('_action' in machine[subject.state]) {
machine[subject.state]._action();
}
}
return subject.state;
},
};
}
const Subject = {
state: '',
counter: 0,
};
const stateEngine = enableButtons(
StateEngine(
{
clear: {
_action: () => {
Subject.counter = 0;
showState(Subject.counter);
},
START: {
state: 'running',
_action: () => Clock.start(),
},
},
running: {
STOP: {
state: 'stopped',
_action: () => Clock.stop(),
},
PAUSE: {
state: 'paused',
_action: () => Clock.pause(),
},
'CLOCK-TICK': {
state: 'running',
_action: () => {
Subject.counter++;
showState(Subject.counter);
},
},
},
paused: {
PAUSE: {
state: 'running',
_action: () => Clock.resume(),
},
},
stopped: {
CLEAR: {
state: 'clear',
},
},
},
'clear',
Subject
)
);
const Clock = (function (stateEngine) {
let timer;
return {
start: startTimer,
stop:...