Clocks and Counters
The Captivate engine can be very powerful for displaying multi-segment real time clocks, counters, and scores with independent animation of all the digits.
This example shows how to create a set of 5 different kinds of clocks and counters with relatively simple code.

Here’s how they work.
Writing a JavaScript Clock
The easiest way to build fast changing, high accuracy clocks and counters is to convert elapsed time into a formatted text string at a reasonably high refresh rate and update the display every time the string changes.
This approach also makes it easy to add specialized logic that is unique to the situation. For example, a countdown clock might switch to a different format, adding tenths of a second, when it reaches the last 10 seconds. Or, it might flip polarity and start counting up as overtime once it passes through 0. There are also great JavaScript libraries for converting time into all kinds of useful formats.
The logic looks something like this:
On start, memorize the current time.
Then, at very frequent intervals, calculate the elapsed time and convert it into a string in whatever format is desired.
Whenever that string changes, schedule an update action with the new time value.
Here’s an example:
// set up time constants in units from milliseconds
const second = 1000;
const minute = 60 * second;
const hour = 60 * minute;
// Initialize the clock string
let clockText = '';
let startTime = Date.now();
// Start the interval timer to check the time every 20 milliseconds
let clockID = setInterval(clockTimer, 20);
function clockTimer() {
timeNow = Date.now();
let elapsedTime = timeNow - startTime;
// Code to convert the elapsed time into a formatted text string
let ms = elapsedTime;
let h = Math.floor(ms / hour).toString();
ms = ms % hour;
let m = Math.floor(ms / minute)
.toString()
.padStart(2, '0');
ms = ms % minute;
let s = Math.floor(ms / second)
.toString()
.padStart(2, '0');
let newClockText = `${h}:${m}:${s}`;
// If the time string actually changed, then send it.
if (newClockText != clockText) {
clockText = newClockText;
// Send this new value to the Input.
scheduler.scheduleAction('update', ServiceHandler.inputName, '', { Clock: clockText });
// Also draw it in the web page
document.getElementById('displayTime').innerHTML = clockText;
}
}
Setting up the Pattern
In order for these clocks to run in real time with multiple animated digits and very little render overhead, the engine first builds a pre-rendered timeline of each individual digit in the clock.
For that to work, it must prepare all the possible values that each individual digit may require. Typically, this includes the numbers 0 through 9, but it may also include alphabetic characters or punctuation like ‘:’ or ‘.’.
To support this, a special pattern string defines all possible values for each digit of the variable.
The following is an example of a pattern for an hour:minute (or minute:second) clock that includes the “am” or “pm” at the end.
let pattern = '[0-2][0-9]:[0-5][0-9][ap]m';
These are the rules for creating a pattern:
[]
denotes a character group
-
inside a character group denotes a range of characters
\
escape the next character and treat it as literal
…
all other characters are treated as literal.
Note 1: When Captivate pre-renders a pattern, it creates a “cell” for each character in the pattern and generates animations for all possible values of that cell. Then, when it receives a value for that variable, it tries to fit the new data into the proper cells. To make this work reliably, always send Captivate data that fits the pattern promised by the Input even if that means adding extra space characters. For the above pattern, to send
"1:23pm"
, it’s recommended to actually set the variable to" 1:23pm"
with a space character before the first digit.
Note 2: Space characters are always considered a possibility for every cell and should not be specified separately, if you want to ensure a space character appears in your final output, use a different character of the proper width to identify the need for spacing.
Here’s an example to keep a space before the am/pm designation of a clock:
let pattern = '[0-2][0-9]:[0-5][0-9][z][ap]m';
There are two ways to set the pattern:
In the XML descriptor for the variable:
<inputBehavior name='API Tour: Clock Example' url='tour/clock.html' UIGroups='General'>
<variable name='Clock 1' category='required' type='text'>
<pattern>[0-9][0-9];[0-9][0-9].[0-9][0-9]</pattern>
</variable>
...
</inputBehavior>
Or, via the scheduler.updateInputDefinition
call:
let variables = {
Message: {
type: 'text',
},
Clock: {
pattern: '[0-9][0-9][:.][0-9][0-9]',
type: 'text',
},
};
// Update the definition of the clock pattern. This triggers a re-render.
scheduler.updateInputDefinition(ServiceHandler.inputName, { variables });
For this example, we use the second option because we actually have five different patterns to use, depending on which mode the user chooses.
Last updated