reptile7's JavaScript blog
Wednesday, January 16, 2019
Keep On Running, Part 2
Blog Entry #392

Welcome back to our ongoing analysis of the Java/JavaScript Goodies Running Calculator. We inventoried the calculator's HTML and reset functionality in the previous post and are now ready to do some actual calculating via the calculator's compute( ) function, which is deconstructed below.

compute( ) it

So, the user enters a series of values into the Input Mileage, Hours, Minutes, and Seconds fields, and then clicks the button, which calls the compute( ) function and passes thereto a this.form reference to the button's containing form, which is given a form identifier.

<script language="livescript">
/* Written by Brad Mueller */
<!-- Hide this script element's contents from old browsers

function compute(form) { /* compute( ) function body */ }

function clear(form) { /* clear( ) function body */ }

<!-- Done hiding from old browsers -->
<input type="button" value="Compute" onclick="compute(this.form);">

In the name of completeness:
• We noted in the Is it live, or is it JavaScript? section of Blog Entry #190 that JavaScript was called "LiveScript" for a short while in 1995. is gone; the parent domain currently redirects to a "dotster" site.
• The Hide this script element's contents from old browsers and Done hiding from old browsers comments are similarly relics from an earlier time: keep them if you want but I'd throw them out.

The compute( ) function body begins with a series of if statements that
checks if the Hours, Minutes, and Seconds fields are blank and
sets them to 0 if they are.

// Test input for error values and make zero if wrong
if (form.hour.value == null || form.hour.value.length == 0) {
    form.hour.value = 0; }
if (form.minute.value == null || form.minute.value.length == 0) {
    form.minute.value = 0; }
if (form.second.value == null || form.second.value.length == 0) {
    form.second.value = 0; }

I'm not quite sure what purpose the form.inputName.value == null tests serve. All <input> values have a string data type and there is to the best of my knowledge no situation in which a string is equal to null: "null" == null returns false, in case you were wondering. The empty string itself does == 0 and false, but that's as close as we get. (An undefined property returns undefined, which is == to null, so if we were to mistype value as, say, valuw or something like that, we'd be OK, but really, isn't it a Webmaster's job to root out that sort of thing before going live with an application?)

Let's move on. The compute( ) function next determines the total running time in seconds and assigns that time to an et variable.

/* Total time in seconds -- note that seconds are multiplied by one so as to convert the second value to a number so it's not concatenated as a string! */
var et = (form.hour.value * 3600) + (form.minute.value * 60) + (form.second.value * 1);

I would have numberized the form.second.value with the Number( ) function but to each his own.

A subsequent if statement
checks if et is not 0 AND if the Input Mileage field is not blank
and if the coast is clear
divides the et time by the mileage input to give a seconds/mile pace that is parked in a t1 variable and then
divides t1 by 60 (a 60 seconds/minute conversion factor) to give the desired minutes/mile pace, which is displayed in the Minutes/Mile (pace) field.

// Make sure we've got everything we need...
if (et != 0 && form.mileage.value.length != 0) {
    // Total seconds/mile
    var t1 = et / form.mileage.value;
    form.minute_mile.value = t1 / 60; }

The compute( ) body concludes with an unnecessary return; statement.

Validation, take two

The hour/minute/second ""0 conditionals are unnecessary as regards the et calculation in that an "" operand in a multiplication or division operation converts to 0, e.g., "" * 3600 returns 0.

If the et time is 0 AND the mileage.value is a positive number, then the Minutes/Mile pace will be 0, which is a legitimate output if not a useful output as it would mean that the user is an infinitely fast runner. Conversely, if the et time is a positive number AND the Input Mileage field is blank, then the form.mileage.value divisor in the t1 calculation will convert to 0 (vide supra) and the Minutes/Mile pace will be Infinity, which is again a legitimate if not useful output as it would mean that the user is an infinitely slow runner.

We do run into trouble output-wise if the Input Mileage → Seconds fields are all blank/0, a situation that would give rise to a NaN Minutes/Mile pace were it not thwarted by the if (et != 0 && form.mileage.value.length != 0) gate.

In an A related aside subsection at the end of Blog Entry #201 I said that
the question "Just how serious am I about what I'm doing?"
was germane to putting or not putting a document type declaration at the top of a document
and we can reasonably apply it to validating or not validating a user's input as well.
The stakes in the present case are pretty small, and Iceman could have not cared about validation at all - "If users are gonna feed bad values into my form, then they forfeit the right to complain if they get bad results: garbage in, garbage out, guys" - nonetheless, he did take a stab at it, and good for him.

You may feel that a more robust validation regimen is in order, however, and I would have to agree. As intimated above:
(m) the mileage input should be a positive number;
(h,m,s) each hour/minute/second input should be a positive number or 0 but they can't all be 0.
As a matter of course we want to intercept the empty string, other non-number inputs (hello world, !@#$%, whatever), and negative numbers. Floating-point positive number inputs are OK and we could truncate the fractional parts of such inputs at, say, the hundredths or thousandths position although doing so would take us into micromanagement territory. Finally, we could place upper bounds on the inputs, but I'm not sure I like that idea: there are ultramarathons that go on for thousands of miles, after all, and we wouldn't want to limit anyone's running aspirations, would we? (Relatedly, I didn't say anything about the <input> sizes when we were going through the HTML - I trust you are up to the task of tweaking them per your preferences.)

The Input Mileage can be managed with:

if (Number(form.elements[0].value) <= 0 || isNaN(form.elements[0].value)) {
    window.alert("The mileage input must be a positive number.");
    form.elements[0].value = "";
    form.elements[0].focus( );
    return; }

A blank mileage is flagged by the Number(form.elements[0].value) <= 0 subcondition because Number("") returns 0. Unhelpfully, isNaN( ) also maps "" to 0 and consequently isNaN("") returns false, so we'll need a separate "" test for the corresponding Hours/Minutes/Seconds check, e.g.:

for (var i = 1; i < form.elements.length - 3; i++) {
    if (! form.elements[i].value.length || Number(form.elements[i].value) < 0 || isNaN(form.elements[i].value)) {
        window.alert("Each time input must be a positive number or 0.");
        form.elements[i].value = "";
        form.elements[i].focus( );
        return; } }

The et != 0 test can be turned around to flag the Hours/Minutes/Seconds-are-all-0 case:

if (! et) { // If et is 0:
    for (var i = 1; i < form.elements.length - 3; i++) form.elements[i].value = "";
    window.alert("At least one of your time inputs must be a positive number.");
    form.elements[1].focus( );
    return; }


It's time to move from theory to practice with a demo that incorporates the validations of the preceding section - try it out below with any inputs you like - check the page source for the full coding.

Running Calculator

In this JavaScript example you will see how fast you've been running those races. Enter your total mileage, hours, minutes, and seconds, and then click the Compute button. Your pace is displayed in the Minutes/Mile (pace) column.

Last-minute modifications:
(1) I use a minute-based
var number_of_minutes = form.hour.value * 60 + Number(form.minute.value) + form.second.value / 60;
calculation in place of the original et calculation and therefore an
if (! number_of_minutes)
gate in place of the above if (! et) gate and an
else form.minute_mile.value = number_of_minutes / form.mileage.value;
clause to display the Minutes/Mile output.
(2) I horizontally center the input labels JavaScriptically via
var labelTds = document.getElementById("firstTr").getElementsByTagName("td");
for (var i = 0; i < labelTds.length; i++) labelTds[i].style.textAlign = "center";
The next Calculators, Calendars, and Clocks item is "World Clock -- Daylight Savings Time" - I'll look it over and see if I can make any sense out of it.

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)