reptile7's JavaScript blog
Sunday, March 10, 2019
 
Times of Our World, Part 3
Blog Entry #395

With the World Clock -- Daylight Savings Time's San Francisco particulars now under our belt, here are some...

Brief notes on the rest of the locale code

One of these offsets is not like the others

The GMT offsets for the remaining locales are:
Denver: hour - 7			London: hour + 0			Bangkok: hour + 7
Memphis: hour - 6		Paris: hour + 1			Tokyo: hour + 9 
New York: hour - 5		Moscow: hour + 3		Sydney: hour + 11
All are OK for standard time except the one for Sydney, whose standard-time GMT offset is actually hour + 10. Not all of Australia observes daylight saving time but New South Wales does, and we can make use of the hour + 11 offset if we want to, depending on the details of the standard time ↔ daylight saving time function we use for Sydney (vide infra).

When I'm twenty-four

Suppose we're in San Francisco and it's a Sunday in January and it's 3 p.m. (sf_hour = 15), for which the par_hour in Paris

var par_hour = hour + 1;

is 24. The Paris day and time display should read

Monday
12:00 a.m.


but in practice it reads

Sunday
12:00 p.m.


A miscoded

if (par_hour > 24) {
    par_hour -= 24;
    par_week += 1; }


conditional employs a >24 comparison to flag the p.m./a.m. boundary when it should use a >23 comparison to do so;
because the par_hour is not greater than 24,
it is not initially dropped to 0 by the -= 24 subtraction and therefore the par_ampm is toggled to  p.m.

if (par_hour > 11) { par_ampm = " p.m."; }

and the par_week is not moved forward a day calendar-wise.
The London, Moscow, Bangkok, Tokyo, and Sydney code units all contain such a conditional although it doesn't cause a problem for those locales in this case because
the London lon_hour (23) is below the 24 threshold whereas
the Moscow mos_hour (26), Bangkok ban_hour (30), Tokyo tok_hour (32), and Sydney syd_hour (34) are all pushed above the 24 threshold by their GMT offsets.
The >24 comparison does cause a problem for those locales when their own *_hours are 24, however, so we of course need to change all those >24s to >23s.

Code consolidation

Upon arraying the dstclock clock's table cells

<table id="worldclockTable" border="1"> ...One row or three, the command below gets/arrays all 10 <td>s... </table>

var localeTds = document.getElementById("worldclockTable").getElementsByTagName("td");


and its GMT offsets

var gmtoffsetsArray = [-8, -7, -6, -5, 0, 1, 3, 7, 9, 10];

and upon generalizing the *_hour, *_week, and *_ampm variables

var locale_hour, locale_week, locale_ampm;

we can merge the San Francisco → Sydney code commands in both the dstclock head and body into a single statement block and automate their execution.

for (var i = 0; i < localeTds.length; i++) {
    locale_hour = hour + gmtoffsetsArray[i];
    locale_week = week;
    ...


You may have noticed that there are no functions in the dstclock code. I first consolidated the San Francisco → Sydney code units via a getLocaleDayTime(gmtoffset, td_index) { ... } function before recognizing that a top-level for loop could give us what we want in a simpler way.

If we recast the weekly day Array as

var weekly = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // Per getDay( )'s indexing

then we can generalize the p.m./a.m. boundary code with

// For San Francisco, Denver, Memphis, and New York
if (locale_hour < 0) {
    locale_hour += 24;
    locale_week = ! locale_week ? 6 : locale_week - 1; }
// For London, Paris, Moscow, Bangkok, Tokyo, and Sydney
if (locale_hour > 23) {
    locale_hour -= 24;
    locale_week = locale_week == 6 ? 0 : locale_week + 1; }
// The ?: conditional operator is documented here.


With GMT-pegged locale_hours in hand we determine the locales' a.m./p.m. designations

locale_ampm = locale_hour > 11 ? " p.m." : " a.m.";

and then move the 13-23 and 0 locale_hours to the 12-hour scale

if (locale_hour > 12) locale_hour -= 12;
if (locale_hour == 0) locale_hour = 12;


and finally assemble the day and time displays and append them to the San FranciscoSydney locale identifiers.

localeTds[i].innerHTML += weekly[locale_week] + "<br>" + locale_hour + ":" + min + locale_ampm; } // End of loop

Speaking of those locale identifiers, could we array them and loop them into, say, font-weight:bold;-ed <span>s? Why, yes, we could do that too.

Stuart did not have a cross-browser/platform way to array the table cells at his disposal - without getting into the details, IE 4 for Windows users could have done so via the document.all mechanism but Netscape/Mac users would have been out of luck - although he could have
loaded the locale displays into text inputs (as was typically done with such scripts back in the day) and
automated the loading action via the Form object's elements property.
Separately writing the displays as #PCDATA/<br> content to the page did at least give him the option of styling them in various ways (e.g., color them with the font element, render them in a monospace font via the tt element) although he chose not to do that.
We've got one last issue to address before rolling out a demo, that being daylight saving time, and we'll hit it in detail in the following entry.

Saturday, February 23, 2019
 
Times of Our World, Part 2
Blog Entry #394

Let's get back now to our discussion of the Java/JavaScript Goodies World Clock -- Daylight Savings Time. In the previous entry we ticked through the dstclock clock's foundational JavaScript, preceding metatext, and HTML scaffolding; in today's post we'll unwind the clock's day and time displays. Three Clock origin values are relevant to those displays:
(w) the week string,
(h) the hour number, and
(m) the min string.

Day prep

The week string contains a three-letter day-of-the-week abbreviation plus a comma:
Sun, or Mon, or ... Sat,

The week day is the day in London. If it's Sunday in London, then it could be Monday in Sydney or Saturday in San Francisco, so we'll need to determine the week + 1 and week - 1 days in addition to the week day. Toward this end, Stuart initially maps the week value to an index number that's one higher than the corresponding getDay( ) index:

if (week == "Sun,") { week = 1; }
if (week == "Mon,") { week = 2; }
if (week == "Tue,") { week = 3; }
if (week == "Wed,") { week = 4; }
if (week == "Thu,") { week = 5; }
if (week == "Fri,") { week = 6; }
if (week == "Sat,") { week = 7; }


The week index is subsequently lowered by one for those users who are using a Macintosh:

// Fix Mac version Communicator bug
function checkOS( ) {
    if (navigator.appVersion.indexOf("Mac") > 0) return "Macintosh";
    else return "other"; }
var check = checkOS( );
if (check == "Macintosh") { week -= 1; }


• If there's a reason to use this code rather than just an
if (navigator.appVersion.indexOf("Mac") > 0) week -= 1;
statement, I don't see it.

• I myself would have reached for the navigator object's platform property to flag Macintosh users (not that we want to be 'platform sniffing' if we can help it, of course) although it is clear from JavaScript Kit's Navigator Object page - see its Additional browsers' Navigator Information subsection - that a navigator.appVersion probe will do the job.

There's no mention of any Macintosh support issues in the toGMTString( ) entry in the JavaScript 1.2 Reference. As Netscape's Communicator/Navigator 4.0-4.05 browsers have a JavaScript 1.2 engine, I downloaded Navigator 4.05 for the Macintosh from OldVersion.com and ran a
window.alert((new Date(2019, 2, 1)).toGMTString( ).split(" ")[0]);
command with it, and the alert( ) box that popped up had a Sat, on it even though 1 March 2019 is a Friday, so there you have it.

We're almost ready to start writing out the locale displays: the missing link between
the full day names in those displays and
the week index
is a weekly Array whose creation follows the checkOS( ) check.

weekly = new Array("Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday");

(Nine elements? Duplicate Saturday and Sunday values? We'll clean this up later.)

More yesteryear matters, in the name of completeness

In his authorship comment, Stuart states that the dstclock code only works with Communicator 4, but should be gracefully ignored by lesser browsers. Actually, there are no features in the dstclock code that are specific to JavaScript 1.2, and I find that
if the week string → index number conversions are formulated as an if ... else if ... else if ... chain
AND
if the script language="javascript1.2" attributes are converted to language="javascript" attributes
then Navigator 3.04, which has a JavaScript 1.1 engine, runs the code without incident.

A bit of sleuthing, with some help from this Netscape onError example, revealed that Navigator 3.04 can't handle the original code for an unexpected reason: once an index number is assigned to week, the subsequent (elseless)
if (week == "Three-letter-day-of-the-week-abbreviation,") comparison
throws a "[day] is not a number" type-conversion error that propagates through the rest of the <script>. If the date object's day is a Friday, then no errors are thrown because the final
if (week == "Sat,") week = 7;
conditional is operative, as it would be for Navigator 4.05 (vide supra).

As it happens, however, Navigator 4.05 and Navigator 3.04 have another toGMTString( ) problem besides the dayday + 1 problem: the time part of the toGMTString( ) return gives the local time and not the time in London, or at least that's what I see in the SheepShaver environment on my computer, for example, for my California locale (GMT - 8)
(new Date(2019, 2, 1)).toGMTString( )
returns
Sat, 02 Mar 2019 00:00:00 GMT
rather than
Fri, 01 Mar 2019 08:00:00 GMT.

Anyway, it's not worth my while (or your while, or anyone else's while) to determine whether the time discrepancy is a browser bug or a SheepShaver artifact in that we've spent enough time with these ancient browsers as it is and we really ought to be leaving them behind, yes? We assume the use of a current browser in the following sections.

If you're going to San Francisco

The day and time display show is ready to hit the road. Here's what we've got for San Francisco:

// In the dstclock head
var sf_hour = hour - 8;
var sf_week = week;
var sf_ampm = " a.m.";
if (sf_hour < 0) {
    sf_hour += 24;
    sf_week -= 1; }
if (sf_hour > 11) { sf_ampm = " p.m."; }
if (sf_hour > 12) { sf_hour -= 12; }
if (sf_hour == 0) { sf_hour = 12; }

<!-- In the dstclock body -->
<td align="center" valign="top"><script language="javascript1.2"><!-- Hide it
document.write("<b>San Francisco</b><br>");
document.write(weekly[sf_week] + "<br>");
document.write(sf_hour + ":" + min + sf_ampm + "<br>");
// --></script></td>


The var sf_hour = hour - 8; line subtracts 8 from the hour in London to give the sf_hour in San Francisco for standard time, but there's no - 7 subtraction for daylight saving time, which is a pretty serious omission given that DST lasts more than half the year.

The var sf_week = week; line gives San Francisco the same day index that London has. If the London hour is in the 0-7 range, then San Francisco will calendar-wise be a day behind London: in this situation
(h) the hour - 8 subtraction gives a negative sf_hour, which is converted to the corresponding 12-hour clock hour by + 24 and - 12 operations, and
(w) the sf_week index is pushed back by one.
The GMT-pegged sf_week is then plugged into the weekly[ ] Array to get the full day name of the display.

An appropriate a.m./p.m. indicator is derived from the sf_hour and assigned to an sf_ampm variable à la the metatext code; moreover, the sf_hour is set to 12 for the midnight to 1 a.m. hour this time.

The day and time, plus a <br> line break that separates them, are written to the San Francisco table cell via separate document.write( ) commands (the concluding <br> in the second write( ) command serves no purpose and can be removed); we'll replace those commands with a tdObject.innerHTML += localeString assignment in due course.

For the last eight hours of Saturday the weekly[sf_week] day is undefined for Mac users as
the initial week index is decreased from 1 for Sunday in London to 0 by the checkOS( ) check and
the initial sf_week index is decreased from 0 to -1 by the if (sf_hour < 0) { ... sf_week -= 1; } conditional;
commenting out the checkOS( ) check changes the undefined to the Saturday we want.
Heading east we've got some serious redundancy on our hands: the dstclock source contains
separate units of code for Denver, Memphis, and New York that
have different hour - x GMT offsets
and of course different variable names (den_hour, mem_hour, etc.)
but are otherwise identical to the above San Francisco code
followed by separate units of code for London, Paris, Moscow, Bangkok, Tokyo, and Sydney that are only slightly more different...
We'll consolidate the lot of it, and sort out the DST thing, in the next post.

Tuesday, February 05, 2019
 
Times of Our World
Blog Entry #393

HTML Goodies' JavaScript Script Tips #87, #88, #89, and #90 discuss a world clock - see it here, get its code here - that we dissected in detail in Blog Entries #102, #103, #104, #105, and #106.

The Java/JavaScript Goodies Calculators, Calendars, and Clocks sector similarly offers a World Clock -- Daylight Savings Time, which was built by Stuart Price and which we'll take on in today's post. Stuart christened his clock WorldTime 1.0DST but I will henceforth refer to it via its dstclock document name. The dstclock clock gives static day and 12-hour time displays for ten locales:
San Francisco, Denver, Memphis, New York, London, Paris, Moscow, Bangkok, Tokyo, and Sydney.
You can see the clock at the aforelinked dstclock.html page and get its code here.

The component parts of each day and time display - the day of the week, the current hour, the current minute, and an appropriate a.m./p.m. designation - are derived from a dateObject.toGMTString( ) return, which is somewhat unusual in that most authors would use more conventional Date methods - specifically, getDay( ), getHours( )*, and getMinutes( ) - to determine those parts. In JavaScript 1.3 toGMTString( ) was deprecated in favor of a new toUTCString( ) method; toGMTString( ) and toUTCString( ) give identical returns with all of the browsers on my computer. The dstclock clock code was written in 1997 during the JavaScript 1.2 era - its various script elements hold a language="javascript1.2" attribute - when toUTCString( ) was not available.
*However, we'll call on getHours( ) when we get the user's local time in the clock preface.

In practice, the dstclock clock works OK some of the time, but it runs into trouble on Saturday or at the p.m./a.m. boundary for some locales, and it actually doesn't deal with daylight saving time at all - we'll get into all of this below.

Clock origin

Our deconstruction begins in the dstclock head this time. The ultimate starting point for every JavaScript clock is the creation of a new Date object:

<head>
<script language="javascript1.2"><!-- Hide it
/* ...Long authorship comment by Stuart... */
var date = new Date( );


The new Date is given a date identifier, which rubs me the wrong way - date is too close to Date for my taste - but is nonetheless legit as date is neither a JavaScript reserved keyword nor a JavaScript future reserved keyword.

We next obtain the toGMTString( ) form of the date Date, and assign it to a timegmt variable.

var timegmt = date.toGMTString( );

The toGMTString( ) method was actually not so well supported back in the day - more on this later - but today it reliably gives the GMT time corresponding to a time - either the current time or some other time - on the user's machine and in a regular, cross-browser format. Accordingly, if you're a user in Escondido, California and it's five in the afternoon on 1 March 2019, then the timegmt string should be:

Sat, 02 Mar 2019 01:00:00 GMT

The timegmt data is subsequently split( ) at its space-character delimiters; the resulting Array of substrings is bound to a time_string variable.

time_string = timegmt.split(" ");

The first five time_string elements are given more descriptive variable names:

week = time_string[0];
day = time_string[1];
mon = time_string[2];
year = time_string[3];
hms = time_string[4];


The hms string - the time part of the toGMTString( ) return - is then split( ) into its hour, minute, and second components, which are bound to an hms_string variable.

hms_string = hms.split(":");

We'll need an hour number to produce the UTC+x locale displays, and subtracting 0 from the hms_string[0] hour value gives us that number, which is stored in a separate hour variable. The hms_string[1] minute value is analogously stored as a string in a min variable.

var hour = hms_string[0] - 0;
var min = hms_string[1];


At a later point we'll map the week string to a getDay( )-like day-of-the-week index; no subsequent use is made of day, mon, and year.

Clock preface

The dstclock page begins with a line of metatext that
includes a local time determination and
is coded by:

<body bgcolor="#ffffff">
<script language="javascript1.2"><!-- Hide me
var loc_hour = date.getHours( );
var loc_ampm = " a.m.";
if (loc_hour > 11) { loc_ampm = " p.m."; }
if (loc_hour > 12) { loc_hour -= 12; }
document.write("<font size='2'>Note: Your local time is </font>");
document.write(loc_hour + ":" + min + loc_ampm);
document.write("<font size='2'> Times listed in the following table are based on your computer's time. If your computer's clock is wrong, so is this table.</font>");
// --></script>


You don't need a detailed play-by-play for this, do you? Didn't think so. But let me make a few points:

• Is it necessary to JavaScriptically write( ) out the non-time part of the text? Is it desirable to shrink that part of the text with the <font size="2"> ... </font> sizing? No and no.

• We can use the ?: conditional operator to set the loc_ampm setting on one line, i.e., var loc_ampm = loc_hour > 11 ? " p.m." : " a.m.";.

• For the midnight to 1 a.m. hour, adding an if (! loc_hour) loc_hour = 12; conditional will make the loc_hour hour 12 rather than 0.

• A loc_hour hour in the 0 loc_hour 9 range won't have a leading 0 because it comes from getHours( ); a min minute in the 00 min 09 range will have a leading 0 because it comes from toGMTString( ).

Clock frame

In the dstclock.txt code the clock is laid out via a one-row, ten-cell table; for the dstclock.html demo the table row is broken into three rows holding four cells, four cells, and two cells, respectively.

<table cellspacing="10">
<tr><td align="center" valign="top">
<script language="javascript1.2"><!-- Hide it
document.write("<b>San Francisco</b><br>");
document.write( /* day and time expression */ );
// --></script></td>
...
<td align="center" valign="top">
<script language="javascript1.2"><!-- Hide it
document.write("<b>Sydney</b><br>");
document.write( /* day and time expression */ );
// --></script></td></tr>
</table>


Can we alternatively use a series of display:inline-block; spans or divs for this purpose? Most certainly, but I will stick with the table in going forward.

The cellspacing attribute of the table element, the align attribute of the td element, and the valign attribute of the td element were all legit in HTML 4 but HTML 5 declares them non-conforming features that must not be used by authors.
• A cellspacing="10" interstitial separation can be achieved with a border-spacing:10px; styling.
• For the td element (and most other alignable elements) an align="center" horizontal centering maps to a text-align:center; styling.
• The ten table cells have a uniform shrink-to-fit height, and therefore the valign="top" attributes have no effect on the cell content (the default valignment is middle but there's just no room to move the content up or down in this case) and can be thrown out. However, if the height of the cell content area were measurably larger than the actual height of the content itself, then the valign="top" attributes could be replaced by a vertical-align:top; styling.

For their part, the San FranciscoSydney locale identifiers can be
written as normal text rather than scriptically and
bolded with a font-weight:bold; styling if desired.
Semantics-wise, are the identifiers heading-y enough to be marked up with one of the h# elements or are they really just label-like captions? I vote the latter and would code them as <label>s if we were loading the clock data into <input>s, although you may feel differently.
Our next task is to disconnect the nuts and bolts of the clock itself: all will be revealed in the following entry.

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
http://iceman.fortunecity.com/
iceman@fortunecity.com */
<!-- 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 -->
</script></head>
...
<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.
http://iceman.fortunecity.com/ is gone; the parent fortunecity.com 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; }


Demo

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.

Monday, December 31, 2018
 
Keep On Running
Blog Entry #391

Sometimes life gets in the way of blogging, and that's been the case for me for the last several months. I hope to detail my recent trials and tribulations in due course at my other blog but for the time being I want to get back into my Web coding efforts. So picking up where we left off last March - vis-à-vis our tour of the Calculators, Calendars, and Clocks sector of Java/JavaScript Goodies - the next list item to go under the microscope is "Running Calculator", which comes to us courtesy of Brad "Iceman" Mueller.

The Running Calculator calculates a runner's minutes/mile pace from mileage, hours, minutes, and seconds inputs; a functional demo appears at the aforelinked running.html page and the calculator code itself can be accessed here. I myself am not a runner and I have no idea if minutes/mile is a standard running metric or not, but my intuition tells me that Roger Bannister would say "Sounds good to me," so let's go with it, shall we?

Calculator intro

The Running Calculator display begins with a big Running Calculator heading and some introductory metatext.

<body>
<h1>Running Calculator</h1>
<font size="3">In this JavaScript example you will see how fast you've been running those races. Enter total mileage, hours, minutes, seconds, and click compute. Your pace is displayed in the Minutes/Mile (pace) column.
<!-- The font element is not closed in the source as it should have been. -->


<font size="3"> ... </font> stylistically maps onto font-size:medium;, medium being the "initial" (default) value of the CSS font-size property, and
HTML 5 says it's OK for text nodes to be children of the body element (the strict flavor of HTML 4 didn't),
so you don't need to mark up the metatext at all (e.g., as a p element) if you don't want to.

Interface HTML

The business end of the Running Calculator HTML is a form/table
whose first row holds an
Input Mileage, Hours, Minutes, Seconds, and Minutes/Mile (pace)
run of captions and
whose second row holds a matching set of text inputs.
In addition, a push button and a button are placed
in the second row in the running.txt code and
in a separate third row for the running.html demo.
The running.txt form/table code is given below.

<br><br><form method="post">
<table border="4">
<tr>
<td><div align="center">Input Mileage</div></td>
<td><div align="center">Hours</div></td>
<td><div align="center">Minutes</div></td>
<td><div align="center">Seconds</div></td>
<td><div align="center">Minutes/Mile (pace)</div></td>
</tr>
<tr>
<td><input type="text" name="mileage" size="9">
<td><input type="text" name="hour" size="6">
<td><input type="text" name="minute" size="6">
<td><input type="text" name="second" size="6">
<td><input type="text" name="minute_mile" size="9">
<td><input type="button" value="Compute" onclick="compute(this.form);">
<td><input type="reset" value="Reset" onclick="clear(this.form);">
</table>
</form></body>


We won't be submitting the input name/value data to a processing agent, and we don't need a form element to render the input elements as Iceman did back in the late 1990s, so we could lose the outer <form> if we wanted to, but I am inclined to keep it as it allows us to very easily add a reset capability without the need for any JavaScript; that said, if you are determined to put a
for (var i = 0; i < runningInputs.length - 2; i++) runningInputs[i].value = "";
reset in the code, well, I won't stop you.

We don't need the <table> either - we could easily replace each column with a corresponding span or div - but because we are working with a grid of data (a small grid of data, yes, but a grid of data nonetheless), I'm inclined to keep that guy too.

Each caption is horizontally centered in its cell by a <div align="center"> ... </div>; much better to give the first-row <tr> an id="firstTr" and then horizontally center all of the captions in one go via a
#firstTr > td { text-align: center; }
style rule.

With those divs out of the way, let's <label> the captions and explicitly associate them with their corresponding controls:

<td><label for="mileage">Input Mileage</label></td>
<td><label for="hour">Hours</label></td>
...
<td><input type="text" id="mileage" name="mileage" size="9">
<td><input type="text" id="hour" name="hour" size="6">
...


We could fairly convert the first-row <td>s to <th>s per this W3C example but as we've got only one data row I'd say we should leave those <td>s be.

As shown above, the second-row <tr> and <td>s are not closed, which is legit although I myself would pencil in their end tags.

The text inputs' type and name attributes are unnecessary - text is the default type value; we could access the inputs ordinally (e.g., document.forms[0].elements[0]) as opposed to associatively via name or id attributes - but I feel no burning need to throw them out.

The Minutes/Mile (pace) output will be inputted into the minute_mile field by a compute( ) function, which we'll go through in our next episode; the minute_mile.value is not meant to be modified by the user so perhaps we should give minute_mile a readonly attribute.

<!-- Old school: -->
<input type="text" name="minute_mile" size="9" readonly>
<!-- New school: -->
<input type="text" name="minute_mile" size="9" readonly="readonly">


The reset, or not

The reset button is equipped with a call to a clear( ) function

function clear(form) { // Set it all to null
    form.mileage.value = "";
    form.hour.value = "";
    form.minute.value = "";
    form.second.value = "";
    form.minute_mile.value = ""; }


that can be sent packing; <input type="reset" value="Reset"> will do the job.

Interestingly, when I recast the original reset button as a
<button type="button" onclick="clear(this.form);">Reset</button>
push button and tried to execute the clear( ) function, nothing happened (it wouldn't clear the mileageminute_mile inputs, an added window.alert("The clear( ) function has been called."); command did not fire). I ran a for...in probe of the window object

<div id="windowmembersDiv"></div>
<script type="text/javascript">
var memberstring = "";
for (var i in window)
    memberstring += "window." + i + " = " + window[i] + "<br />";
document.getElementById("windowmembersDiv").innerHTML = memberstring;
</script>


to check for a clear member; a
window.clear = function clear(form) { /* clear( ) function body */ }
line duly appeared in the memberstring output. A clear function javascript Google search led me to a relevant "Is 'clear' a reserved word in JavaScript?" Stack Overflow page at which commenter Felix Kling points out that onclick="clear( );" actually triggers the obsolete document.clear( ) method vis-à-vis a global clear( ) function because the document object comes before the window object in the onclick event handler's "scope chain". Fortunately, bypassing document.clear( ) is no more difficult than prepending a window object reference to the onclick clear( ) call, i.e., <button type="button" onclick="window.clear(this.form);">Reset</button>.
We'll discuss the Running Calculator's JavaScript, and perhaps roll out our own demo, in the following entry.

Monday, March 05, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 8
Blog Entry #390

We return now to the js-caltop1.html document and its isnform form, whose
(mm) selection list allows us to change the calendar month and
(yyyy) and buttons allow us to change the calendar year.

Changing the calendar month

var t = new Date( );
var v = t.getMonth( ) + 1;
function navigator(isnform) {
    var durl = (isnform.isnlist.options[isnform.isnlist.selectedIndex].value);
    document.line.month.value = durl; }
document.write("<form name='isnform'>");
document.write("<select name='isnlist' onchange='navigator(this.form);'>");
document.write("<option value='1'>January");
document.write("<option value='2'>February");
...
document.write("<option value='12'>December");
document.write("<\/select>");
document.isnform.isnlist.selectedIndex = v - 1;


The isnlist menu is initialized to the v - 1th option for the current month (March at present). Choosing a new month option calls a navigator( ) function that gets the new option's value and stores it in the line form's month field via the intermediacy of a durl variable. As detailed in Part 5 and Part 6 of this series, the document.line.month.value is used to determine
(i) the day of the week that the first day of the calendar month falls on and
(ii) how many days the calendar month has
upon setting the calendar code in motion, and is ultimately placed in the calendar caption.

Today we can directly pass this.value to the navigator( )changeMonth( ) function - recall that the pre-DOM client-side Select object didn't have a value property - subsequently, I would numberify* the newOptionValue and then assign it to v and, as v has a global scope, just work with v versus the month field in going forward.

function changeMonth(newOptionValue) { v = Number(newOptionValue); }

<select name='isnlist' onchange='changeMonth(this.value);'>


Other codings are of course possible but this is as simple and straightforward as any.

*Re the days_in_previous_month calculation in the previous post, a string newOptionValue leads to NaNs and not date numbers in December cells of January calendars.

Changing the calendar year

Sandwiched between the and buttons is a showyear text field whose value is initialized to '19' + t.getYear( ) so as to display the current year number.

var y = t.getYear( );
document.write("<input name='raise' type='button' value='<<' onclick='down( );'>");
document.write("<input name='showyear' type='text' value='19" + y + "' size='4'>");
document.write("<input name='raise' type='button' value='>>' onclick='up( );'>");
document.write("<\/form>");


Clicking the button calls an up( ) function that increases the showyear.value year number by one and then stores the incremented number in the line form's year field.

var th = 0;
var th2 = -1;
var qw = 0; /* No subsequent use is made of qw. */
var q = new Date( );
var newyr = q.getYear( );
function up( ) {
    th++;
    document.isnform.showyear.value = "19" + (newyr + th);
    if (document.isnform.showyear.value > 1999) {
        th2++;
        if (th2 < 10) { th2 = "0" + th2; }
        document.isnform.showyear.value = "20" + th2; }
    document.line.year.value = document.isnform.showyear.value; }


Again, the document.line.year.value is used to determine the day of the week that the first day of the calendar month falls on and is ultimately placed in the calendar caption.

Clicking the button calls a down( ) function that decreases the showyear.value year number by one and stores the decremented number in the year field: it replaces th++ with th-- and th2++ with th2-- but is otherwise identical to the up( ) function.

This code was adequate back in the day but even without access to the getFullYear( ) method there was room for improvement here. The up( ) and down( ) functions use "19", "0", and/or "20" strings and number-holding th and th2 variables to piece together a 4-digit year as a numeric string - not the most efficient way of doing things, is it? Instead, we could get a 4-digit year number and store it in the newyr variable and the showyear field from the get-go via:

var t = new Date( );
var newyr = t.getYear( );
if (0 <= newyr && newyr <= 99) newyr += 1900; /* No adjustment is necessary for years after 1999 or before 1900. */
document.isnform.showyear.value = newyr;


We can increment or decrement a 4-digit newyr in a merged up_or_down( ) function

function up_or_down(adjustYear) {
    adjustYear == "up" ? newyr++ : newyr--;
    document.isnform.showyear.value = newyr; }

<input name="lower" type="button" value="<<" onclick="up_or_down('down');"> ...
<input name="raise" type="button" value=">>" onclick="up_or_down('up');">


and then work with newyr versus the year field in going forward.

The if (0 <= newyr && newyr <= 99) newyr += 1900; getYear( ) adjustment for the years 1900-1999 is needed for JavaScript 1.0-1.2; a var newyr = 1900 + t.getYear( ); statement would correctly handle all years for later versions of JavaScript à la var newyr = t.getFullYear( );, which is what we should be using, needless to say.

A little reminder: You'll get Gregorian calendars and not Julian calendars if you take the year back to 1752 or earlier, e.g., February 1700 will have 28 days.

One more point before moving on:
The user can impart focus to the showyear field and change its value but the calendar code will not detect that change. It would be a good idea to give the field a readonly attribute; alternatively, we could convert the field to a newyr-holding <samp> or <span> although I think the former looks nicer on the page than does the latter.

No reset

Preceding the up( ) and down( ) functions in the js-caltop1.html source is a reseter( ) function

function reseter( ) {
    document.line.year.value = "19" + t.getYear( );
    document.isnform.showyear.value = "19" + t.getYear( ); }


that resets the year and showyear fields' values to "19" + t.getYear( ) when the user unloads (exits) the js-caltop1.html document.

<body onload="date( );" bgcolor="black" onunload="reseter( );" text="white">

The reseter( ) function serves no useful purpose, and may be safely discarded.

Demo

Not mentioned previously:
Pagetools.de has posted a Multiple Java Calendar demo here: its date string and calendar parts are marred by getYear( ) vs. getFullYear( ) calls but at least the clock part is OK.

We conclude our Multiple Java Calendar odyssey with our own demo - check the page source for the full coding.

This Is A Simple Calendar That You Can Change To Any Month Or Year You Want.

Demo notes

(1) The left, center, and right cells of the js-caltop1.html layout table are now clock_and_date_string, month_and_year_controls, and calendar_trigger divs, respectively. A display:inline-block; styling places the divs in the same line box; a vertical-align:top; styling aligns their top edges; their heights are harmonized with:

document.getElementById("calendar_trigger").style.height =
document.getElementById("month_and_year_controls").style.height =
document.getElementById("clock_and_date_string").clientHeight + "px";


(2) On updating a calendar, the to-be-changed calendar's date rows are removed with:

var calendarTable2 = document.getElementById("calendarTableId2");
for (var i = calendarTable2.rows.length - 1; 0 < i; i--) {
    calendarTable2.removeChild(calendarTable2.rows[i]) };


It's necessary to start at the last row and go upward in doing this; a top-down approach removes only every other row.

(3) The current day's date number is reddened for only the current month

datecolor2 = b2 - c2 == dates2 && t2.getMonth( ) == today2.getMonth( ) && t2.getFullYear( ) == today2.getFullYear( ) ? "red" : "black";

whereas the original code reddened it for all months.
I'll be working at my (seriously neglected) other blog for a while.

Friday, February 16, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 7
Blog Entry #389

In today's post, we will create a calendar for February 2018 with the remaining part of the js-calbot2.html* <script>.

*If you're using a current version of Firefox, Google Chrome, or Opera, then you should be able to view the js-calbot2.html source here; however, you won't see anything at the js-calbot2.html page itself (other than its black background) as the calendar makes use of data generated in the js-caltop1.html document.

We coded and rendered a calendar caption and row of Sunday to Saturday day headers in the previous post: these features will sit atop a date section comprising a 4-to-6-row grid of cells à la a regular calendar. Two key pieces of information enable us to build the date section:
(1) the number of days in the calendar month, which is stored in an a variable;
(2) the getDay( ) index for the first day of the calendar month, which is stored in a d variable.
Our a value is 28 as 2018 is not a leap year; our d value is 4 as 1 February 2018 fell on a Thursday.

The pre-months display

The date section begins in the original calendar table's rows[2] row. The code below writes out four empty td cells for the Sunday to Wednesday days in January that precede 1 February 2018. The c variable serves as an index of sorts: it's 1 for the rows[2].cells[0] cell, 2 for the cell after that, etc.

var c = 0;
while (c < d) {
    c++;
    document.write("<td></td>"); }


• The rows[2] row's <tr> and </tr> tags are both missing in the source: the latter is in fact optional but the former is required.
• I'll show you how to place date numbers (28, 29, 30, 31) in these cells later.

February

After c reaches d (4) and the td for 31 January is written out, the script moves to a b variable to direct the formation of the February part of the date section.

var b = 0;
b = b + c;


Also a cell index of sorts, b picks up where c leaves off and steps us from 1 February 2018 (b = 5) to 28 February 2018 (b = 32).

var t = new Date( );
var week = t.getDay( ) + 1; /* No subsequent use is made of week. */
var day = t.getDate( );
var e = a + c; /* e marks the end of the months month. */
while (b < e) {
    b++;
    if (b == 8 || b == 15 || b == 22 || b == 29 || b == 36) { document.write("<tr>"); }
    if ((b - c) == day) { color = "red"; }
    else { color = "white"; }
    document.write("<td><center><font color='" + color + "' face='Arial'><b>" + (b - c) + "</b></font></center></td>"); }
document.write("</tr></table>");
</script> /* That's it for the js-calbot2.html script. */


• The Saturday bs are multiples of 7; when b++ takes b one above those values, the current row is ended and a new row is started.
• The b - c difference gives the date number for each cell.
• The date numbers are horizontally centered (td content aligns left by default), colored - today's date number, day, is red, all other date numbers are white - Arialized, and bolded.

The post-months display

When the while (b < e) { ... } loop has run its course, control passes to a document.write("</tr></table>"); command that closes the current row and the calendar table itself. The last day of February 2018 is a Wednesday, and no cells are written out for the last row's Thursday, Friday, and Saturday - there's just blank space there. Nonetheless, it is simple enough to code date cells for the first three days of March:

.postmonths { color: #449977; font: bold medium Arial; text-align: center; }

while (b % 7) {
    b++;
    document.write("<td class='postmonths'>" + (b - e) + "<\/td>"); }


• This loop effectively continues the previous one, moving b beyond e to the next multiple of 7, 35 in this case.
The caption's teal-ish #449977 color seems right for the March date numbers, don't you think?

Then came the last days of January

Now, what about the date numbers for the pre-months cells? To get those numbers, we'll need to know how many days the previous month had. Toward this end, we could go through the monthsa if...else if...else cascade all over again, but working with a corresponding monthdaysArray would be a much more efficient way to go:

var monthNumber = t.getMonth( );
var monthdaysArray = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((! (yr % 4) && (yr % 100)) || ! (yr % 400)) monthdaysArray[1] = 29;
var days_in_previous_month = monthNumber ? monthdaysArray[monthNumber - 1] : 31;


With the days_in_previous_month number in hand, we can write( ) the cells + date numbers for the last four days of January with:

.premonths { color: #449977; font: bold medium Arial; text-align: center; }

while (c < d) {
    c++;
    document.write("<td class='premonths'>" + (days_in_previous_month - d + c) + "<\/td>"); }


Demo

It's time to put it all together via a demo, wouldn't you say?
(This post was written in February 2018; the calendar will update to whatever month it happens to be.)


• The demo is framed by an id="calendarDiv" div.
• Per the original design, the demo content and the styles applied to that content are written JavaScriptically without the aid of any non-script HTML/CSS.
• The document.write( ) operations have been replaced by corresponding document.createElement( )/Node.appendChild( ) operations. Excepting the table border setting, all styles are set via the Element.style.CSSProperty = "value" mechanism.

var calendarTable = document.createElement("table");
calendarTable.id = "calendarTableId";
calendarTable.border = "1";
calendarTable.style.marginLeft = "auto";
calendarTable.style.marginRight = "auto";
document.getElementById("calendarDiv").appendChild(calendarTable);
...

We're not quite done with the Multiple Java Calendar: we have yet to discuss the calendar's month and year controls in the center cell of the js-caltop1.html layout table, and we'll do that in the next post.


Powered by Blogger

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