Wednesday, April 20, 2016
Did We Really Count to Five Hundred?
Blog Entry #365
In today's post we'll go through the Calendars, Clocks, and Calculators (CCC) sector's "Count It!" script. Crafted by "Tigger" in March 1998, the Count It! script counts up from 0 to an inputted positive integer via a running display of integers in a text box. The javagoodies.com/countit.txt document holding the Count It! script code is still live as of this writing.
The script demo at the "Tigger's Counting Script" countit.html page works in a sense but not satisfactorily so. If you input, say, 25 in the Count to: field and then click the button, your 25 will instantly appear in the below-the-button field and you won't see any counting at all, that is, the 0 → 24 intermediate numbers zoom by much too fast for you to see them. We'll slow things down below.
Control HTML
The user enters the target number into a name="number" text input; the counting takes place in a name="output" text input. A name="form" form contains the number field, a value=" Count! " push button, and the output field. The display is horizontally centered on the page by a center element.
<center>
<form name="form"><br><br><br>
Count to:<input type="text" name="number" length="3">
<input type="button" value=" Count! " onclick="go(document.form.number.value);">
<input type="text" name="output">
</form></center>
• Neither the input element nor any other HTML element has a length attribute; as the target number is capped at 500 (vide infra), I suspect Tigger meant maxlength rather than length.
• FWIW, the starting space character and the ending space character of the " Count! " button label are rendered by all of the browsers on my computer except Firefox.
Let's go( )
Clicking the button calls a go( ) function and passes thereto the number field's value, which is given a number identifier.
<script language="JavaScript">
function go(number) { ... }
</script>
</head>
The go( ) function body first tests if number is greater than 500; if the test returns true, then
(a) a "That's pretty high, so I'll set it to 500" alert( ) pops up and
(b-c) number and the number field's value are lowered to 500.
if (number > 500) {
window.alert("That's pretty high, so I'll set it to 500.");
number = 500;
document.form.number.value = "500"; }
• number initially has a string data type and is converted to a number for the number > 500 comparison.
This is as much validation as we get from the go( ) function: there are no checks for non-numeric strings, floating-point numbers, or negative numbers.
With number capped at 500, the for statement below increments an n counter from 0 to number and sequentially loads the n values into the output field.
for (n = 0; n <= number; n++) {
document.form.output.value = n;
window.setTimeout("", 500); }
A setTimeout( ) command whose expression parameter is set to an empty string and whose msec parameter is set to 500 is meant to serve as a delaying tactic that allows us to see discrete ns as the loop runs. It doesn't work in practice, and you wouldn't expect it to work in practice, because...
window.setTimeout( ) does not stall the script. The script continues immediately (not waiting for the timeout to expire). The call simply schedules a future event.So, we schedule
""
to happen in 500 milliseconds and long before we get to that ""
n will have reached number and the latter is all we see. Did the delaying work once upon a time when processors were more primitive than what we have today? Maybe, but that was then and this is now.More generally, we shouldn't be using a loop to count n in the first place: loops are meant to iteratively execute one or more statements as quickly as possible vis-à-vis in a controlled, gradual manner. However, this doesn't mean that we have to start from scratch to sort out the situation; we can achieve the desired effect if we
(1) have the setTimeout( ) command recursively call go( ),
(2) externalize the n initialization, and
(3) deploy the remaining parts of the loop in a corresponding if conditional.
var n = 0, countID;
function go(number) {
...number-vetting code...
if (n <= number) {
document.form.output.value = n;
n++;
countID = window.setTimeout(function ( ) { go(number) }, 500); }
else n = 0; }
Validation
Let's go back to the original script for a moment. If we leave the number field blank and click the button, then our empty string 'input' is converted to 0 for the n <= number loop condition; in this case, the loop runs for one iteration and document.form.output.value = 0 is as high as we go.
All other non-numeric inputs convert to NaN for the n <= number condition. The 0 <= NaN comparison returns false and therefore the loop doesn't run for any iterations (the condition is evaluated a single time and that's all that happens) and no counting occurs. FYI: According to Netscape,
NaN is not equal to anything, including NaN.
For a negative number input, the n <= number condition again returns false from the get-go and no counting occurs. (But perhaps you might like to count down to a negative number - we go in reverse here.)
For a floating-point number input, go( ) counts to Math.floor(Number(number)), which is OK, but I'd rather it count to Math.round(Number(number)).
All things considered, here's the number-vetting code I'd use:
if (Number(number) < 0 || 500 < Number(number) || isNaN(number) || number === "") {
window.alert("Please enter a positive integer in the range:\n0 \u2264 n \u2264 500");
document.form.number.value = "";
document.form.number.focus( );
return; }
if (number.toString( ).indexOf(".") != -1) {
number = Math.round(Number(number));
document.form.number.value = number;
window.alert("We're going to round your input to: " + Math.round(number)); }
• Upon round( )ing a floating-point number input in a first go( ) iteration it is necessary to toString( ) number for the indexOf( ) test in subsequent go( ) iterations. Alternatively, if we deparameterize go( ) and (re-)type number as a string at the beginning of the go( ) body via
var number;
function go( ) {
number = document.form.number.value;
...
countID = window.setTimeout(go, 500);
... }
<input type="button" value=" Count! " onclick="go( );">
then the toString( ) conversion is unnecessary.
• \u2264 is the Unicode escape sequence for a ≤ character.
Bells and whistles
Stop it
You type 500 in the number field and click the button. When n hits 100 you think, "All right, I get the idea, let's pull the plug on this." For stopping the count in midstream we can add:
function stopCount( ) { window.clearTimeout(countID); }
<input type="button" value="Stop the Count" onclick="stopCount( );">
Start over
We naturally want to add a reset capability to the display. A complete reset requires us to not only blank the number and output fields but also send n back to 0.
If we hold onto the form form, then adding our reset is no more complicated than appending an
<input type="reset" value="Reset" onclick="n=0;">
child to the form.In the demo below, I replace the form and its
<center>
parent with a <div style="text-align:center;">
container, give the number field an id="numberInput", and exchange the output field for an id="outputSamp" samp placeholder, and I accordingly clear the decks with:function resetCount( ) {
n = 0;
document.getElementById("numberInput").value = "";
document.getElementById("outputSamp").textContent = ""; }
Count to: <input id="numberInput" name="number">
The current count is: <samp id="outputSamp"></samp>
<button type="button" onclick="resetCount( );">Reset</button>
Color it
My demo randomly colors the n numbers with code borrowed from the HTML Goodies JavaScript Script Tips #81-83 script; the colors are accentuated by beefing up the n font-size to 48px.
#outputSamp { font-size: 48px; }
var defClrsArray, colorIndex;
defClrsArray = ["red", "purple", "aqua", "green", "blue", "fuchsia", "orange", "brown"];
function go( ) {
...
if (n <= number) {
colorIndex = Math.floor(Math.random( ) * defClrsArray.length);
document.getElementById("outputSamp").style.color = defClrsArray[colorIndex];
document.getElementById("outputSamp").textContent = n;
n++;
countID = window.setTimeout(go, 500); } ... }
Demo
The current count is:
The financial countdown
On the CCC portal page, Joe comments that the Count It! script isuseless - but fun. Hmmm... I think of the downward counts in the "Fast Money" part of Family Feud. If you won $20,000 by giving the right answers to questions during those counts, that wouldn't be so useless, would it? So let's put 20 seconds on the clock, which will start after I read the first question and you click the button below...
The current count is:
00:20
(I trust you can write out the code for this, yes?)
We'll briefly discuss some more geometry-related scripts and then perhaps start work on a calculator script in the following entry.
Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)