reptile7's JavaScript blog
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.

Wednesday, January 31, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 6
Blog Entry #388

The right year

One last point regarding the compute( ) day-of-the-week algorithm discussed in the previous post:
Obviously, the algorithm will not give us what we want if the val3 year input is off in some way. The year field's "19" + t.getYear( ) value was 1997 back in 1997 but is 19118 in 2018, which for January leads to a week = Tuesday versus the Monday that 1 January 2018 actually fell on, not good: this problem can be quickly fixed by setting the value to t.getFullYear( ).

A new charge

After the compute( ) function has finished executing, a

window.open('js-calbot2.html', target='frame2');

command replaces the js-calbot1.html document in the frame2 frame with the js-calbot2.html document. Per Mozilla, the window.open( ) method's windowName parameter specifies the name of the browsing context, a <frame> in this case, into which to load the specified resource; the target= part is unnecessary and in fact shouldn't be there.

js-calbot2.html intro

The js-calbot2.html body comprises a single script element that single-handedly (without the aid of any non-script HTML) creates a tabular calendar for a given month.

<html>
<body bgcolor="black" text="red" link="white" vlink="white">
<script>
...calendar code...
</script>
</html>


Body style

As shown above, the body element start tag provides color settings for the page background, the foreground text, unvisited links, and visited links.
(t) The text="red" attribute reddens the calendar's Sunday to Saturday day headers, and that's it.
(l,v) There are no links on the js-calbot2.html page.

Interframe data transfer

The js-calbot2.html script calls on the values stored in the line form's result, month, and year fields to create the calendar.

window.onerror = null;
first = parent.frame1.document.line.result.value;
months = parent.frame1.document.line.month.value;
yr = parent.frame1.document.line.year.value;


The line form's raison d'être is to facilitate this transfer of data. However, we could alternatively store the line form's hidden control values in global variables

var val1, val2x, val3, week;
val1 = 1;
val2x = t.getMonth( ) + 1; // Or just: val2x = t.getMonth( );
val3 = t.getFullYear( );


in which case there would be no need for the line form at all.

var first = parent.frame1.week;
var months = parent.frame1.val2x;
var yr = parent.frame1.val3;


The window.onerror = null; statement means your users won't see JavaScript errors caused by your own code, quoting Netscape, or at least that's what it meant for users who were using Netscape 3.x-4.x back in the day. I suspect the statement's purpose is to suppress the "parent.frame1 is undefined" or equivalent error that would otherwise be promptly thrown by the above parent.frame1 expression(s) if we were to land on the isolated js-calbot2.html page outside of the javacalendar.html frameset; a much better approach to preempting that error is to route the user to javacalendar.html via an if (! parent.frame1) window.location = "javacalendar.html"; conditional.

What month is it?

To build the calendar we'll need to know how many days the months month has.

var a = "";
if (months == 1 || months == 3 || months == 5 || months == 7 || months == 8 || months == 10 || months == 12) { a = 31; }
else if (months == 4 || months == 6 || months == 9 || months == 11) { a = 30; }
else { a = 28; }


Minor bullet point:
• The number-storing a can be declared with a var a; statement; its initialization to an empty string is pointless.
Major bullet point:
No allowance is made for leap years, whose Februaries will have only a = 28 days per the concluding else clause. Here's what we want for February:

else {
    if ((! (yr % 4) && (yr % 100)) || ! (yr % 400)) a = 29;
    else a = 28; }


We will later put the name of the months month in the calendar table caption. Accordingly, the months month number is mapped onto a corresponding month string via:

if (months == 1) { months = "January "; }
else if (months == 2) { months = "February "; }
...
else if (months == 12) { months = "December "; }


We saw the same set of months mappings in the js-caltop1.html date string code: as in that case, I would alternatively use an Array to get the months string:

var t = new Date( );
var monthArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var months = monthArray[t.getMonth( )];


Re the pre-months display

The first day string is mapped onto a getDay( )-synced d index via:

if (first == "Sunday") { d = 0; }
else if (first == "Monday") { d = 1; }
...
else if (first == "Saturday") { d = 6; }


We could have gotten d back in the compute( ) function with:

var t = new Date(val3, val2x, val1);
var d = t.getDay( );


We will later use d to create calendar table cells that lead up to the start of the months month for months months that do not begin on a Sunday.

Calendar intro

We're ready to start writing out the calendar table.

document.write("<center><table border='1'>");

• The center element's closing </center> tag is missing in the source; of course, we should actually use a table { margin-left: auto; margin-right: auto; } styling to horizontally center the table.

The calendar's rows[0] row holds one cell that contains a months yr caption.

document.write("<tr>");
document.write("<th colspan='7'><center><font size='+3' color='#449977' face='Arial'><b>" + months + " " + yr + "</b></font></center></th>");
document.write("</tr>");


• Th cell content is bolded and horizontally centered by default with Firefox, Google Chrome, Opera, and Safari on my computer.
• A +3 font element size can be effected by a font-size: xx-large; styling.
• A caption is a heading but not really a header, and the th element is not quite right for it; it would be better to put our caption in a bona fide caption element:

caption { color: #449977; font: bold xx-large Arial; }

var calendarCaption = document.createElement("caption");
calendarCaption.textContent = months + " " + yr;
document.getElementsByTagName("table")[0].appendChild(calendarCaption);


Caption content is placed at the top of the table and is horizontally centered but not bolded by default; it is not surrounded by the table border, which doesn't bother me, although you could give it, say, a border: double #888; styling if you like.

The calendar's rows[1] row holds seven cells that respectively contain Sunday to Saturday day headers.

document.write("<tr>");
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Sunday</b></font></center></td>");
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Monday</b></font></center></td>");
...
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Saturday</b></font></center></td>");
document.write("</tr>");


• Changing the td elements to th elements would allow us to get rid of the center and b element formatting.
• A -1 font element size can be effected by a font-size: small; styling; that said, I myself would size-wise leave the day headers alone and not small-en them.

Here's what we've got so far:


The day header row is coded with:

var dayArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var dayTr = document.createElement("tr");
document.getElementsByTagName("table")[0].appendChild(dayTr);
for (var i = 0; i < dayArray.length; i++) {
    var dayTh = document.createElement("th");
    dayTh.style.width = "75px";
    dayTh.textContent = dayArray[i];
    dayTh.style.color = "red";
    dayTh.style.fontFamily = "Arial";
    dayTr.appendChild(dayTh); }

We'll go through the date part of the calendar in the following entry.

Sunday, January 14, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 5
Blog Entry #387

We'll finish off the js-caltop1.html document's scripts[2] script in today's post. I've done some homework on that mysterious code in the compute( ) function and I've got it all pretty well sorted out at this point - here we go...

More on the line form

A proper discussion of the compute( ) function obliges us to flesh out the line form that code-wise surrounds it.

As detailed earlier in this series, the line form contains five hidden controls and the push button.

The hidden part of the form is written JavaScriptically via a document.write( ) command that appears
(a) after a t = new Date( ); constructor, whose t Date provides values for the form's month and year fields, and
(b) before the compute( ) function
in the scripts[2] script.

The button and the line form's closing </form> tag, and the cells[2] table cell that contains them, are written as normal HTML that appears outside and after the scripts[2] script.

We can code the line form more cleanly if we put all of it in the cells[2] cell and separate its HTML and JavaScript as follows:

<td id="rightTd">
<form name="line">
<input name="res" type="hidden">
<input name="day" type="hidden" value="1">
<input name="month" type="hidden">
<input name="year" type="hidden">
<input name="result" type="hidden">
<input name="butt" type="button" value=" Show Calendar " onclick="compute(this.form); window.open('js-calbot2.html', target='frame2');">
</form>
<script type="text/javascript">
var t = new Date( );
document.line.month.value = t.getMonth( ) + 1;
document.line.year.value = "19" + t.getYear( );
</script>
</td>


In the compute( ) function, we will use the values of the day, month, and year fields to find the starting day of the week for a given month; this determination will enable us to build a calendar for the month from scratch in the frame2 frame.

compute( ) it

References

(1) The compute( ) code is based on an algorithm developed by Kim Larsen in a 1995 "Computing the Day of the Week" article; the compute( ) code differs slightly from Larsen's algorithm in that the former is pegged to a 0 = Saturday to 6 = Friday week whereas the latter is pegged to a 0 = Monday to 6 = Sunday week.

N.B. A significant part of the third paragraph of Larsen's article ("More formally, ...") is not rendered due to an incorrectly coded <img> element (that points to an image of a ≤ character, which could have been coded with a &le; character reference) - you'll have to go to the page source to get the full text.

(2) Ask Dr. Math's "The Calendar and the Days of the Week" article is a good starting point if you are new to day-of-the-week algorithms (as I was); it notably links to
(a) a "Formula for the First Day of a Year" article that compares Larsen's algorithm with one version of* a more well-known day-of-the-week algorithm called Zeller's congruence (*note that Ask Dr. Math's Zeller formula is different from Wikipedia's Gregorian Zeller formula) and
(b) a "Deriving Zeller's Rule" article that is helpful in understanding the month part(s) of the Larsen and Zeller algorithms.

Deconstruction

The Larsen and Zeller algorithms calculate a day-of-the-week index for the day of the week for a specific date. In our case, the compute( ) function will determine the day of the week that the first day of the current month falls on - "Monday" for January 2018, e.g. - if we haven't touched the month and year controls in the cells[1] table cell; if desired, however, compute( ) and its inputs can be modified so as to determine the day of the week for any Gregorian date.

Upon clicking the button and calling the compute( ) function, a this.form reference to the line form is passed to compute( ) and given a form identifier. As noted at the end of the previous post, the compute( ) body begins by changing the Show Calendar button label to Update Calendar.

function compute(form) {
    document.line.butt.value = "Update Calendar";


After that, compute( ) gets down to business. A subsequent set of operations extracts date, month, and year inputs for the compute( ) day-of-the-week code from the line form; for the arithmetic to come, these inputs are stored in val1, val2x, and val3 variables, respectively.

var val1 = parseInt(form.day.value, 10);
if ((val1 < 0) || (val1 > 31)) { window.alert("Day is out of range."); }
var val2 = parseInt(form.month.value, 10);
if ((val2 < 0) || (val2 > 12)) { window.alert("Month is out of range."); }
var val2x = parseInt(form.month.value, 10);
var val3 = parseInt(form.year.value, 10);
if (val3 < 1900) { window.alert("You're that old!"); }


• The day, month, and year values have a string data type; we'll carry out some addition with the numbers they hold in a little bit so it is necessary to numberify them via either the parseInt( ) function or the Number( ) function.
• The day value is fixed at 1 - at no point in the code is it reset - and therefore the if ((val1 < 0) || (val1 > 31)) test can be thrown out.
• The if ((val2 < 0) || (val2 > 12)) test can also be thrown out: the month value can either stay at t.getMonth( ) + 1 or be reset by the isnlist selection list but at no point does it go outside the 1-12 range.
• It's actually not necessary to use two variables for the month input: the values of val2 and val2x will diverge for January and February (while remaining the same for all other months) but that divergence can be recoded with one variable.
• You can push the val3 value below 1900 if you click the button enough times but the You're that old! alert( ) response upon doing that seems a bit silly, doesn't it?

The compute( ) day-of-the-week code works with a year of months that run from the March of a given year to the February of the next year and that are numbered 3 to 14, respectively. The January and February of the given year are renumbered and shifted to the previous year by the following conditionals:

if (val2 == 1) {
    val2x = 13;
    val3 = val3 - 1; }
if (val2 == 2) {
    val2x = 14;
    val3 = val3 - 1; }


The compute( ) day-of-the-week code works with a week of days that run from Saturday to Friday and that are numbered 0 to 6, respectively. The val1 1 date is mapped onto its corresponding day number via:

var val4 = parseInt(((val2x + 1) * 3) / 5, 10);
var val5 = parseInt(val3 / 4, 10);
var val6 = parseInt(val3 / 100, 10);
var val7 = parseInt(val3 / 400, 10);
var val8 = val1 + (val2x * 2) + val4 + val3 + val5 - val6 + val7 + 2;
var val9 = parseInt(val8 / 7, 10);
var week = val8 - (val9 * 7);


The terms that go into the var val8 = val1 + (val2x * 2) + val4 + val3 + val5 - val6 + val7 + 2; line are not meaningful in an absolute sense; rather, they are part of a formula that tracks changes in the day of the week in response to various time markers.
(val2x * 2) + ((val2x + 1) * 3) / 5 moves the day of the week forward by three days or two days as we go from month to month in the March to February year.
• In going from year to year, val3 + val3 / 4 - val3 / 100 + val3 / 400 moves the day of the week forward by one day if val3 ends with a 28 February (365 % 7 = 1) or two days if val3 ends with a 29 February.
• The + 2 at the end syncs us with a 0 = Saturday to 6 = Friday week; Larsen's formula doesn't have it; a + 1 would sync us with the getDay( ) method's 0 = Sunday to 6 = Saturday week.
Math.floor( ) can be used in place of parseInt( ).
• The last two commands - var val9 = parseInt(val8 / 7, 10); var week = val8 - (val9 * 7); - are equivalent to var week = val8 % 7;.

The val1 date's day number, week, is stored in the line form's res field and then mapped onto a corresponding day string via a switch-like set of if statements; the resulting week day string is finally stored in the line form's result field.

form.res.value = week;
if (week == 1) { week = "Sunday"; }
else if (week == 2) { week = "Monday"; }
else if (week == 3) { week = "Tuesday"; }
else if (week == 4) { week = "Wednesday"; }
else if (week == 5) { week = "Thursday"; }
else if (week == 6) { week = "Friday"; }
else if (week == 0) { week = "Saturday"; }
form.result.value = week; } /* That's it for compute( ). */


A better getDayString( )

We earlier created
(1) a separate t Date just for the line form and
(2) a var dayArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; Array for the date string part of the code;
both t and dayArray have a global scope. With these features in hand, we can get the week day string with:

t.setDate(1);
var week = dayArray[t.getDay( )];


That's it - a lot simpler, huh? This is something the author could have done: the setDate( ) method of the Date object was implemented in JavaScript 1.0 whereas the core Array object was implemented in JavaScript 1.1.
We are at long last ready to load the js-calbot2.html document into the frame2 frame and take on its calendar code, and we'll do just that in our next episode.

Wednesday, December 27, 2017
 
A JavaScript 1.2 Calendar and Clock, Part 4
Blog Entry #386

Image clock miscellany

I've got a bit more to say about the Multiple Java Calendar script's image clock before we move on to its calendar.

Image availability

For whatever reason, JavaScript Goodies is missing the following image clock images:
(1) the dgcol.gif colon image;
(2-4) the dgm.gif, dga.gif, and dgp.gif a.m./p.m. images (all three of them); and
(5-6) the dg-1.gif and dg-2.gif digit images.
The other digit images and the dgbl.gif and calcbl.gif blanks can be accessed and downloaded via their https://www.htmlgoodies.com/JSBook/image_file_name URLs, e.g., https://www.htmlgoodies.com/JSBook/dg7.gif will take you to the 7 image.

In contrast, all of the image clock images are available at Pagetools.de's "Index of /java/scripts4/kalend3" directory - get them there.

Image dimensions

Excepting the dgcol.gif image, all of the image clock images are dimensions-wise 16px by 21px. As detailed here in Blog Entry #384, the non-colon images are all loaded into 12px by 15px <img>s. Do we really want to be shrinking these guys? I'd say no, they're small enough as it is, although you may feel differently.

For its part, the dgcol.gif image is 11px by 21px and is loaded into an <img> whose height is again 15px but whose width is unspecified; in this case I would halve the original width to 6px and leave the original height alone.

Image preloading

The Script Tips #84-86 script preloads the digit and a.m./p.m. images but the js-caltop1.html scripts[0] script doesn't. Given modern processor speeds, and that all of the images for both scripts have a 4 KB file size, you wouldn't think preloading would be necessary, but that said, it certainly isn't a bad idea by any stretch to preload the aforementioned images, and doing so only takes a few lines of code:

/* Place these statements at the beginning of the scripts[0] script, just before the date( ) function declaration: */
var digitImgs = new Array(10);
for (var i = 0; i < digitImgs.length; i++) { digitImgs[i] = new Image( ); digitImgs[i].src = "dg" + i + ".gif"; }
var amImg = new Image( ), pmImg = new Image( );
amImg.src = "dga.gif"; pmImg.src = "dgp.gif";
/* There's no point in preloading the dg-1.gif and dg-2.gif images if we don't need to use them in the first place. */


HTML vs. JavaScript images

If the image width and height information is moved to a style sheet or isn't specified at all, then the clock <img>s can be written as normal HTML, which would be my preference. On the other hand, there's something to be said for coding the clock as an externalizable JavaScript widget, in which case I would lose the document.write( ) action in favor of its modern-day DOM equivalent:

<div id="clockDiv"></div>
...
var imgNames = ["hour1", "hour2", "colon", "min1", "min2", "sec1", "sec2", "noon1", "M"];
for (var i = 0; i < imgNames.length; i++) {
    var clockImg = document.createElement("img");
    clockImg.name = imgNames[i];
    if (i == 2) clockImg.src = "dgcol.gif";
    else if (i == 8) clockImg.src = "dgm.gif";
    else clockImg.src = "calcbl.gif";
    document.getElementById("clockDiv").appendChild(clockImg);
    if (i == 4 || i == 6) document.getElementById("clockDiv").appendChild(document.createTextNode(" ")); }


With today's browsers, var clockImg = new Image( ); can be used in place of var clockImg = document.createElement("img");.

The head form

As detailed in the previous post, a head form containing four hidden controls

<form name="head">
<input name="hour" type="hidden">
<input name="min" type="hidden">
<input name="sec" type="hidden">
<input name="noon" type="hidden">
</form>


precedes the scripts[0] script. Near the end of the date( ) function - between the sec if...else if...else if... cascade and the window.setTimeout("date( );", 1000); recursive date( ) call - is a set of three statements

document.head.hour.value = hour;
document.head.min.value = min;
document.head.sec.value = sec;


that assigns hour, min, and sec to the values of the first three head inputs. No subsequent use is made of the head data set and consequently all of this code can be thrown out.

We will later use the hidden inputs of the line form to pass day, month, and year information to the js-calbot2.html calendar code and I suspect the author similarly wanted to make the hour/min/sec data available to the js-calbot2.html source via the head form even though js-calbot2.html does not in practice act on that data.

In the name of completeness

• Because we want a new image in the <img name="sec2"> placeholder every second and because a date( ) run does take a finite amount of time, I would argue that the 1000 setTimeout( ) delay should be slightly decreased, say, to 900. Moreover, Mozilla would recommend that we convert window.setTimeout("date( );", 900); to window.setTimeout(date, 900);.

• Finally, I don't like the date( ) function name - we're getting the time, not the date - we should change it to createClock( ) or something like that. Even if I were inclined to keep the date( ) name, W3Schools.com advises, You should [also] avoid using the name of JavaScript built-in objects, properties, and methods [as variables, labels, or function names], and date( ) is identifier-wise too similar to Date( ) for my taste.

Pre-calendar

Clicking the button

<input name="butt" type="button" value=" Show Calendar " onclick="compute(this.form); window.open('js-calbot2.html', target='frame2');">

in the right cell of the js-caltop1.html layout table calls a compute( ) function and loads the js-calbot2.html document into the frame2 frame of the javacalendar.html frameset. The compute( ) function is the last function in the js-caltop1.html source and is the only function in the scripts[2] script; it works with the hidden inputs of the line form

/* Upon closing the layout table's center cell: */
yr = document.isnform.showyear.value;
t = new Date( );
document.write("<form name='line'>");
document.write("<input name='res' type='hidden'>");
document.write("<input name='day' type='hidden' value='1'>");
document.write("<input name='month' type='hidden' value='" + (t.getMonth( ) + 1) + "'>");
document.write("<input name='year' type='hidden' value='19" + t.getYear( ) + "'>");
document.write("<input name='result' type='hidden' value=''>");
document.line.year.value = yr;
function compute(form) { ... }


to determine the day of the week that the first day of the calendar month will fall on, e.g., "Friday" for December 2017.

As shown above, a run of top-level code prior to the compute( ) declaration
(i) assigns the showyear text field's value, 19117 for the year 2017, to a yr variable,
(ii) constructs a new t Date object, which supersedes the t Date object constructed in the scripts[1] script,
(iii) writes out the line form's start tag and hidden inputs, and
(iv) assigns yr to the value of the year hidden field.

The compute( ) body begins simply enough by switching the butt button label to Update Calendar.

document.line.butt.value = "Update Calendar";

Let's wait until the next post to discuss the rest of compute( ), which is kind of...strange: strange because of its inexplicable arithmetic and strange because we could be getting the desired day of the week string with just a few lines of code vs. the 30+ lines that compute( ) uses.

Thursday, December 14, 2017
 
A JavaScript 1.2 Calendar and Clock, Part 3
Blog Entry #385

We continue today our discussion of the image-based digital clock ("image clock" hereafter) on the left side of the Multiple Java Calendar script's js-caltop1.html display. We wrote out the clock's underlying HTML and rendered its initial form in the Clock intro section at the end of the previous post, and we are now ready to set the dynamic parts of the clock via the js-caltop1.html date( ) function.

This will be our second go at an image clock script: In Blog Entries #101 and #359 we created an image clock via a script that
(ST) is discussed by HTML Goodies' JavaScript Script Tips #84, #85, and #86, and
(CCC) is also provided by the Calendars, Clocks, and Calculators sector(s) of JavaScript/Java Goodies.
If truth be told, the Script Tips #84-86 script is much better written than the js-caltop1.html date( ) function, and we will be calling on the former as is appropriate in the deconstruction below.

date( ) intro, noon1, hour

The date( ) function is the first of six functions in the js-caltop1.html source and is the only function in the scripts[0] script. As befits a clock-creating function, the date( ) function is a recursive function, and it is first called when the js-caltop1.html document has loaded.

<body onload="date( );" bgcolor="black" onunload="reseter( );" text="white">
<form name="head"> <!-- We'll get rid of this form later. -->
<input name="hour" type="hidden">
<input name="min" type="hidden">
<input name="sec" type="hidden">
<input name="noon" type="hidden">
</form>
<script> /* Neither a language attribute nor a type attribute is provided for any of the js-caltop1.html script elements. */
var a = 12;
var b = 15;
function date( ) {
    ...clock-creation code...
    window.setTimeout("date( );", 1000); /* Updates the clock every 1000 milliseconds. */ }


Like every other clock script we've covered previously, the date( ) function codes a 12-hour clock, and it loads a dga.gif A image or a dgp.gif P image for a.m. or p.m. respectively into the <img name="noon1"> placeholder. The first date( ) statement assigns dga.gif as a default of sorts (without regard to the hour of the day) to the src of the noon1 img.

document.noon1.src = "dga.gif";

Next, date( ) constructs a today Date object and then gets hour, minute, and second information for that object.

today = new Date( );
hour = today.getHours( );
min = today.getMinutes( );
sec = today.getSeconds( );


We previously constructed a today Date object having a global scope in the date string part of the code; that today would allow us to create a static time display but to create a running clock it is necessary to construct a new Date again and again.

If the getHours( ) return is in the 13-23 range - if we're somewhere between 1 p.m. and midnight - then 12 is subtracted from the return and the noon1 img's src is switched to dgp.gif.

if (hour > 12) {
    hour = today.getHours( ) - 12;
    document.noon1.src = "dgp.gif"; }


• Do we need to call getHours( ) a second time? Nope, hour -= 12; would give us what we want.
• The midnight to 1 a.m. hour is left at 0: it is not subsequently normalized to 12.
document.noon1.src is not subsequently switched to dgp.gif for the noon to 1 p.m. hour (12).

For the hour part of the clock, date( ) loads
(t) either the dgbl.gif blank or a dg1.gif 1 image into the tens-place <img name="hour1"> placeholder, and
(o) a dg0.gif-dg9.gif 0-9 image into the ones-place <img name="hour2"> placeholder.

if (hour < 10) {
    document.hour1.src = "dgbl.gif";
    document.hour2.src = "dg" + hour + ".gif"; }
else {
    document.hour1.src = "dg1.gif";
    document.hour2.src = "dg" + (10 - hour) + ".gif"; }


• The Multiple Java Calendar file set includes a calcbl.gif blank that doesn't have the faint character template present in dgbl.gif; you may want to use the former in place of the latter.
• For the 11 and 12 hours the (10 - hour) subtraction gives -1 and -2, respectively; leaving aside that we could just reverse the order of operands ((hour - 10)), the Multiple Java Calendar file set includes a dg-1.gif 1 image and a dg-2.gif 2 image to accommodate these hours.

OK, let's tighten this up. We'll begin with the a.m./p.m. part because - is everyone writing this down? - if you're coding a 12-hour clock with an a.m./p.m. part, then the a.m./p.m. part is in fact the first part you should deal with. The Script Tips #84-86 script does this indirectly via an amPM variable; we'll use a direct approach. In the present case, one simple line of code gives the right a.m./p.m. setting for every hour of the day:

document.noon1.src = hour > 11 ? "dgp.gif" : "dga.gif";

Upon bringing all of the hours into the 1-12 range

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


we can similarly handle the hour part with:

document.hour1.src = hour < 10 ? "calcbl.gif" : "dg1.gif";
document.hour2.src = hour < 10 ? "dg" + hour + ".gif" : "dg" + (hour - 10) + ".gif";


The ?: conditional operator is documented here; it is precedence-wise lower than both </> and +.

As we'll be using the % remainder operator below, let me lastly note that we can alternatively set the hour2.src for every hour of the day via:

document.hour2.src = "dg" + hour % 10 + ".gif";

min, sec, demo

(Near the end of Blog Entry #382 I warned you that the js-caltop1.html source is 'highlighted' by some major-league bloat. Here we go, folks...)

For the min and sec parts of the clock, date( ) loads
(t) a dg0.gif-dg5.gif image into the tens-place <img name="min1"> and <img name="sec1"> placeholders, and
(o) a dg0.gif-dg9.gif image into the ones-place <img name="min2"> and <img name="sec2"> placeholders.
Each part is handled by a sprawling, 11-clause if...else if...else if... cascade.

if (min < 10) {
    document.min1.src = "dg0.gif";
    document.min2.src = "dg" + min + ".gif"; }
else if (min == 10) {
    document.min1.src = "dg1.gif";
    document.min2.src = "dg0.gif"; }
else if (min < 20) {
    document.min1.src = "dg1.gif";
    document.min2.src = "dg" + ((10 - (30 - min)) + 10) + ".gif"; }
...
else if (sec == 50) {
    document.sec1.src = "dg5.gif";
    document.sec2.src = "dg0.gif"; }
else if (sec < 60) {
    document.sec1.src = "dg5.gif";
    document.sec2.src = "dg" + ((10 - (70 - sec)) + 10) + ".gif"; }


The tens-place assignments are straightforward enough, but if there's any rhyme or reason to those ((10 - (x - min|sec)) + 10) ones-place calculations, I don't see it. In any case, our study of the Script Tips #84-86 script gratifyingly enables us to replace each cascade with two lines of code:

document.min1.src = "dg" + Math.floor(min / 10) + ".gif";
document.min2.src = "dg" + min % 10 + ".gif";
document.sec1.src = "dg" + Math.floor(sec / 10) + ".gif";
document.sec2.src = "dg" + sec % 10 + ".gif";


We're good to go at this point. Here's what our clock should look like:


Demo confession:
Blogger's weird image-upload URLs, which I discuss in Blog Entry #359, required me to abandon the
imageObject.src = "dg" + x + ".gif" assignments
in favor of
imageObject.src = dg[x] assignments,
dg being an array of the digit images, à la the Script Tips #84-86 script - a blogger's gotta do what a blogger's gotta do - still, what you see is what you get if you are able to make use of the former.


Powered by Blogger

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