reptile7's JavaScript blog
Monday, December 11, 2017
 
Image Clock Beta
We're good to go at this point. Here's our clock:


Thursday, November 30, 2017
 
A JavaScript 1.2 Calendar and Clock, Part 2
Blog Entry #384

Welcome back to our ongoing analysis of the Calendars, Clocks, and Calculators Multiple Java Calendar script. At present we are working our way through the js-caltop1.html document that fills the upper frame1 frame of the javacalendar.html frameset.

As noted in the previous post, the js-caltop1.html body contains three script elements, which I will call scripts[0], scripts[1], and scripts[2], as though we had gotten them with a var scripts = document.getElementsByTagName("script"); command. (FYI: Use of the id attribute with the script element wasn't OK in HTML 4 but is green-lighted by HTML5.)

The left cell of the js-caltop1.html layout table is coded completely by the scripts[0] script, whereas the center cell of the table is coded partly by the scripts[1] script and partly by the scripts[2] script. The right cell of the table is written as normal HTML.

The month and year controls

The document.write( ) command that writes out the

"<font face='Arial'><b>" + day + months + dates + "/" + years + "</b></font>"

date string in the layout table's left cell also writes out nine img placeholders for the image-based digital clock above the date string; the clock itself is filled in by a date( ) function that fires when the js-caltop1.html page has finished loading. Before we get to the clock, however, we've got some more top-level rendering to do.

The scripts[0] script ends with the document.write("</td>"); closure of the left cell. The scripts[0] script's nextElementSibling is the scripts[1] script, which writes out some of the layout table's center cell.

The center cell content comprises a form that holds four controls, in source order:
elements[0] is a selection list of month options.

elements[1] is a push button.
elements[2] is a text field that displays a getYear( ) year number.

elements[3] is a push button.

The scripts[1] part of the center cell is:

<script>
...
document.write("<center><td><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>");
</script>


• The starting center element cannot validly be a child of the layout table's rows[0] row (the tr element has a (th|td)+ content model) and does not horizontally center the rows[0].cells[1] content. Throw it out.
• À la the date string's months = today.getMonth( ) + 1; index, each month option value is one higher than the corresponding getMonth( ) return.

The isnlist list's options can alternatively and preferably be created via the selectObject.options-writing methodology detailed in Blog Entry #380:

var monthArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var optionvaluesArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; /* As per getMonth( ) */
for (var i = 0; i < monthArray.length; i++)
    document.isnform.isnlist.options[i] = new Option(monthArray[i], optionvaluesArray[i]);


The scripts[1] script ends with the document.write("</select>"); closure of the isnlist list. The scripts[1] script's nextElementSibling is the scripts[2] script, which writes out the rest of the center cell.

t = new Date( );
y = t.getYear( );
...
<script>
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>");
document.write("</td>");
...
</script>


In the js-caltop1.html source, the <input ... value='<<' ...></td> run of tags is actually written out by a single document.write( ) command, which I have broken up for the sake of clarity.

The value attribute of the input element has a CDATA data type designation and therefore there shouldn't be a problem with specifying literal < and > characters in the value values for the and buttons but you might want to err on the side of caution and use character references to code those characters, e.g., value='&lt;&lt;' and value='&gt;&gt;'.

The t = new Date( ); and y = t.getYear( ); statements actually appear near the beginning of the scripts[1] script, although they could have been placed at the beginning of the scripts[2] script. The y year number is effectively appended to a 19 substring and the resulting numeric string is assigned to the value of the showyear text field. For all but a small set of very early browsers, the 2017 y return is 117 and the initial showyear value is therefore 19117.

The today = new Date( ); and years = today.getYear( ); statements in the date string part of the code have a global scope and could have been used to set the showyear value. That said, if we were to separate the code's calendar, clock, and date string parts from one another, then each part would of course need its own Date functionality.

The navigator( ), up( ), and down( ) functions are coded in the scripts[1] script; we'll go through these functions after we display the calendar in the frame2 frame.

Before we move on to the layout table's right cell, I should note that the isnlist selection is set to the current month by:

v = t.getMonth( ) + 1;
...
document.isnform.isnlist.selectedIndex = v - 1;


The v getMonth( ) determination follows the t construction in the scripts[1] script, whereas the selectedIndex assignment occurs at the very end of the scripts[2] script. A document.isnform.isnlist.selectedIndex = t.getMonth( ); command at the end of the scripts[1] script would have been simpler, yes?

The calendar trigger

The right cell's only content is a push button.

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


The button is the last control of a line form whose start tag and first five controls are written JavaScriptically in the scripts[2] script

document.write("<form name='line'>");
document.write(...5 type='hidden' <input>s...);


even as the button and the following </form> tag are written as normal HTML as shown above - not a valid situation* whether the script element has a CDATA or #PCDATA content model.

*We could say the same thing about the layout table itself, most of which is created JavaScriptically but whose last cell and concluding markup are written as normal HTML. We will get rid of the document.write( ) commands, the layout table, and the isnform and line forms when it comes time to roll out a demo.

The compute( ) function is coded in the scripts[2] script; we'll discuss compute( ) and that somewhat unusual window.open( ) command when we're ready to display the calendar.

Clock intro

The layout table is followed by three <p>s of text that take us to the end of the js-caltop1.html source and that I trust you are able to modify (or delete) as you see fit. When the top-level rendering is finished, the browser gets to work on the clock in the table's left cell. As noted earlier, the clock's img placeholders are written out by the same scripts[0] document.write( ) command that writes out the date string.

var a = 12;
var b = 15;
...
/* As before, the original code has been broken up below to make it more readable. */
document.write("<img name='hour1' src='dgbl.gif' width='" + a + "' height='" + b + "'>");
document.write("<img name='hour2' src='dgbl.gif' width='" + a + "' height='" + b + "'>");
document.write("<img src='dgcol.gif' height='" + b + "'>");
document.write("<img name='min1' src='dgbl.gif' width='" + a + "' height='" + b + "'>");
document.write("<img name='min2' src='dgbl.gif' width='" + a + "' height='" + b + "'> ");
document.write("<img name='sec1' src='dgbl.gif' width='" + a + "' height='" + b + "'>");
document.write("<img name='sec2' src='dgbl.gif' width='" + a + "' height='" + b + "'> ");
document.write("<img name='noon1' src='dgbl.gif' width='" + a + "' height='" + b + "'>");
document.write("<img src='dgm.gif' width='" + a + "' height='" + b + "'>");


Seven of the nine placeholders are initially filled with a 'blank' dgbl.gif image. A dgcol.gif image of a colon fills the third placeholder and a dgm.gif image of an uppercase M fills the ninth placeholder. Note that a space separates (v-vi) the fifth and sixth placeholders and (vii-viii) the seventh and eighth placeholders.


(Yes, the dgcol.gif image really is that faint. Also, the original bgcolor="black" rubs me the wrong way and we'll definitely change that when it comes time to roll out a demo.)
We'll check over the date( ) function that sets the hour, minute, second, and a.m./p.m. parts of the clock in the following entry.

Tuesday, November 14, 2017
 
A JavaScript 1.2 Calendar and Clock, Part 1
Blog Entry #383

We introduced the Multiple Java Calendar script in the previous entry and are now ready to deconstruct the script's frame documents; we'll go through js-calbot1.html and the date string part of js-caltop1.html in today's post.

The font-size follies

The js-calbot1.html source is much simpler than that for js-caltop1.html or js-calbot2.html, and contains no JavaScript, so that's where we'll start.

<html>
<!-- This script was made by Ryan Haugh. -->
<head><title></title></head>
<body bgcolor="black" text="white">
<center>
<font size="+4,+5" face="Arial">This Is A Simple Calendar That You Can Change To Any Month Or Year You Want.</font>
</html>


• An authorship comment containing the author's full name appears in all three Pagetools.de frame documents but not in the corresponding JavaScript Goodies documents.
• Yep, the </center> tag is missing - we'd need it if we were keeping the center element but we won't be doing that.

We've got a white, horizontally centered, gigantic Arial text string on a black background - that's it. HTML5 declares that the bgcolor and text attributes of the body element, the center element, and the font element are obsolete ... and must not be used by authors, but let's leave this aside for a moment. The one odd thing here is that size="+4,+5" attribute - what does it mean, what exactly does it give us?

The +4,+5 value is not invalid in that the font element's size attribute has a CDATA data type designation but it's not really valid, either: unlike the face="Arial" attribute, whose value could be a comma-separated list of font names in order of preference, the size attribute should have only one setting.

A size="+4" setting would mean "four sizes larger" and take us from a size="3" default to a size="7" maximum vis-à-vis an integer size scale that ranges from 1 to 7 and to which all sizes belong, quoting the W3C; it follows that a size="+5" setting would also take us to the size="7" maximum, i.e., +5 wouldn't go off-scale and give us an even bigger size.

To see how a +4,+5-type value plays out in an on-scale case, I applied size="+2", size="+2,+3", and size="+3" settings to some normal (size="3") text: I found that the size="+2" and size="+2,+3" renderings were identical and were smaller than the size="+3" rendering, as though the browser parseInt( )ed the +2,+3 value.

If in the js-calbot1.html case the size="+4", size="+4,+5", size="+5", and size="7" settings give identical renderings, then in the name of simplicity we might as well size="7" the This Is A Simple Calendar ... string if we're going to hold onto the font element. But of course, we shouldn't hold onto the font element, or the center element or the body element's bgcolor and text attributes for that matter; as the center element is a type of div element, we should code the js-calbot1.html body with:

body { background-color: black; color: white; }
#aboutDiv { text-align: center; font-size: 48px; font-family: Arial; }
...
<div id="aboutDiv">This Is A Simple Calendar That You Can Change To Any Month Or Year You Want.</div>


This Is A Simple Calendar ...

At least on my computer, a size="7" setting is effectively duplicated by a font-size:48px; style declaration; CSS's largest <absolute-size> font-size value, xx-large, maps onto size="6" and therefore doesn't quite cut it.

What day is today?

The first-completed part of the js-caltop1.html display is the date string in the layout table's left cell. Most of the js-caltop1.html display is created JavaScriptically, and the date string is created completely by top-level code in the first of three script elements in the js-caltop1.html body.

We begin by creating a today Date object and then getting day of the week, month, date number, and year information for that object.

<body onload="date( );" bgcolor="black" onunload="reseter( );" text="white">
...
<script>
...
today = new Date( );
day = today.getDay( ) + 1;
months = today.getMonth( ) + 1;
dates = today.getDate( );
years = today.getYear( );


The author increments the getDay( ) and getMonth( ) returns to numbers that a non-programmer would associate with a given day of the week and month respectively, e.g., day is 1 for a Sunday, months is 11 for November. This is a bad call in my book: when writing JavaScript code it is best to stick with JavaScript's starts-with-0 indexing system.

The Multiple Java Calendar script went live at Java Goodies in November 1997 and predates the implementation of the getFullYear( ) method in JavaScript 1.3; the years getYear( ) return would have been 97 in 1997.

Moving on, the day and months indexes are respectively converted into corresponding day of the week and month strings by two series of if statements.

if (day == 1) { day = "Sunday, "; }
else if (day == 2) { day = "Monday, "; }
...
else if (day == 7) { day = "Saturday, "; }

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


Finally, day, months, dates, and years are plugged into the b element child of a font element that concludes (is the lastChild of) the layout table's left (rows[0].cells[0]) cell.

document.write("<center>");
document.write("<table border='0' width='100%'>");
document.write("<tr>");
document.write("<td align='middle'>");
document.write(...img placeholders for the image-based digital clock...);
document.write("<br>");
document.write("<font face='Arial'><b>" + day + months + dates + "/" + years + "</b></font>");
document.write("</td>");
</script>


• In the js-caltop1.html source, the <center></td> run of tags is actually written out by a single document.write( ) command, which I have broken up for the sake of clarity.
• The beginning center element horizontally centers the text that follows the layout table but has no effect on the layout table itself, which spans the width of the viewport as per its width='100%' attribute.
• The td content is in practice horizontally centered by the align='middle' attribute even though align should have been set to center for this purpose. FYI: The align attribute was valid for the various internal table elements in HTML 4 but has now been obsoleted by HTML5 for all of the elements that could formerly take it.

So, if we retro-set today to 1 November 1997 (new Date(1997, 10, 1)), then our output is:

Saturday, November 1/97

How I'd code the date string

body { background-color: black; color: white; }
#datestringDiv { font-weight: bold; font-family: Arial; }
...
<div id="datestringDiv"></div>
...
var today = new Date( );
var dayArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var day = dayArray[today.getDay( )];
var monthArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var months = monthArray[today.getMonth( )];
var dates = today.getDate( );
var years = today.getFullYear( );
document.getElementById("datestringDiv").textContent = day + ", " + months + " " + dates + ", " + years;


I like working with arrays and I use a getDay( )-synced dayArray and a getMonth( )-synced monthArray to set day and months, respectively. I of course replace the getYear( ) call with a getFullYear( ) call. I house the date string in a div, which can be created JavaScriptically although I prefer to write it as normal HTML. Re the date string format, I separate dates and years with a comma-space versus a slash.


Tuesday, October 31, 2017
 
Triangles to Dates
Blog Entry #382

OK, what's next in the Calendars, Clocks, and Calculators (CCC) sector? Preceding the Money Conversion Script is an Area of a Triangle script that calculates the area of a triangle from window.prompt( )-gathered base and height inputs. The Area of a Triangle script code was authored by Greg "Dabomb" Bland and is available in unadulterated form at the JavaScript Goodies site here and is reproduced below in its entirety for your convenience.

<script language="javascript">
function area( ) {
    var base = window.prompt("Enter the base of the triangle.");
    var height = window.prompt("Enter the height of the triangle.");
    var sum = (base * 0.5) * height;
    window.alert("The area is " + sum + ". This was solved by using a = (1/2)bh."); }
function about( ) {
    window.alert("This script was created by Greg Bland. dabomb@lorien.ml.org"); }
</script></head>
<body>
<form>
<input type="button" value="Area of Triangle" onclick="area( );">
<input type="text" name="areaf">
</form><font color="blue"><i><center>


The above area( ) function is more or less identical to the triangle area-calculating areati( ) function of The Areas, a CCC script we covered in Blog Entries #361 and #362; that said, the Area of a Triangle script went live at Java Goodies about five months earlier than The Areas did.

In practice, the Area of a Triangle demo at the aforelinked triangle.html page works fine as far as it goes, although it doesn't place any validation-type constraints on the user's prompt( ) inputs. (If you input hello for the base and world for the height, then you'll get a The area is NaN output on the post-calculation alert( ) box.) I'm not going to give you my own Area of a Triangle demo but will instead direct you to the corresponding #triangleDiv demo in Blog Entry #362.

Code miscellanea

• There's nothing present that calls the about( ) function, but I trust you're up to the task of adding an about( )-calling button to the interface.

• The areaf text field is superfluous as things stand, but there's nothing stopping you from keeping that field and adding a second one for inputting the base and height respectively in place of the prompt( ) commands.

• Regarding the stray <font color="blue"><i><center> markup at the end, if you do want to blue, italicize, and/or horizontally center some text on the page, then you should of course use a style sheet to do that.

Returning to the CCC portal, the Area of a Triangle listing is followed by one for a Multiple Java Calendar script.

The Multiple Java Calendar link at the javagoodies.com/ccc.html page points to a javacalendar.html frameset

<frameset rows="35%,65%">
<frame name="frame1" src="js-caltop1.html" scrolling="no" marginwidth="0" marginheight="0" border="0">
<frame name="frame2" src="js-calbot1.html" scrolling="auto" marginwidth="0" marginheight="0">
</frameset>


whose upper frame at one time held a js-caltop1.html document and whose lower frame at one time held a js-calbot1.html document; both of these documents now redirect to a developer.com/java portal.

Fortunately, the corresponding javacalendar.html frameset at JavaScript Goodies is still intact. Here's what we've got for a display:

(frame1) A one-row, three-cell table lays out the action part of the js-caltop1.html page. The right cell features a button for showing a calendar in the frame2 frame; the center cell contains controls for changing the calendar month and year; at no extra charge, the left cell sports an image-based digital clock, some of whose images are missing, and a date string for the current date.

(frame2) The js-calbot1.html page's only content is a styled This Is A Simple Calendar That You Can Change To Any Month Or Year You Want. text string.

The script calendar is actually assembled for the most part by a separate js-calbot2.html document that is loaded into the frame2 frame when the button is clicked. (Oddly, js-calbot2.html is still accessible at the Internet Archive.)

Some text below the aforementioned js-caltop1.html table identifies the script's author as "Haugh" and notes that the script's constituent files were originally bundled for download at http://www.geocities.com/SunsetStrip/Alley/1679/calender.zip, a resource that is now gone. However, I recently stumbled upon a http://www.pagetools.de/java/scripts4/kalend3/ page that provides the script files (minorly modified in places) both separately and as a kalend3.zip package. Danke schön, Pagetools.de!

Over and above the script's
(a) use of a frameset (vide infra),
(b) commingling of structure, presentation, and behavior, and
(c) getYear( ) calls (nope, the current year is neither 117 nor 19117),
the js-caltop1.html source is 'highlighted' by some major-league bloat and the most inscrutable block of JavaScript arithmetic I have ever seen. There's plenty here to keep us off the streets for the next several posts.

Let me wrap up this entry with some brief commentary on the javacalendar.html frameset itself:

The HTML frame element doesn't have a border attribute, but it does have a boolean-like frameborder attribute whose value can be 1 (the default) or 0. To get rid of the frame1-frame2 separator, it is necessary to set the frameborder for both frames to 0.

• Much more importantly, HTML5 obsoletes the frameset and frame elements; we will replace the frame1 and frame2 frames with divs in due course.

Wednesday, October 04, 2017
 
It's Only Money (Part 3)
Blog Entry #381

Let's get back now to the Money Conversion Script and see if we can make it better. Paralleling our deconstruction two entries ago, we'll begin our efforts by tightening up the Unit and Unit2 selection lists and their options and after that we'll modernize the Compute( )/roundToPennies( ) functionality that processes the user's inputs.

Structure streamline

No form

Will we at any point submit the script's successful control data to a processing agent? No, we won't be doing that. CompuH@cker needed a <form> to render the text fields, selection lists, and button, but we don't. Accordingly, let's
(a) lose the MoneyForm form and
(b) give ids to the text input elements (→ moneyInput1, moneyInput2) and select elements (→ moneySelect1, moneySelect2) so we can access them directly.

Option consolidation

When I first looked at the Unit and Unit2 menus I mused, "This is redundant, the script is coding the same menu twice, the only difference being that the option values in the second list are reciprocals of those in the first list." That those relative currency values are ensconced in the script HTML didn't sit well with me either - there ought to be an easier, cleaner way to maintain that information, yes?

I gave fleeting thought to organizing the currency data as an associative array à la the HTML Goodies Script Tips #56-59 guitar chord chart script

var currencyOptionData = new Object( );
currencyOptionData["DZD Algerian Dinars"] = 2900.0000;
currencyOptionData["USD American Dollars"] = 170000.00;
...


but quickly realized I wouldn't be able to work with the data iteratively that way; alternatively, if the currencyOptionData Object is recast as a two-dimensional Array

var currencyOptionData = new Array( );
currencyOptionData[1] = ["DZD Algerian Dinars", 2900.0000];
currencyOptionData[2] = ["USD American Dollars", 170000.00];
...


then we can smoothly create and deploy the Unit and Unit2 options in one go via:

<select id="moneySelect1" name="Unit" size="1">
<option value="Alert" selected>- Choose Currency Unit Below -</option>
</select>

<select id="moneySelect2" name="Unit2" size="1">
<option value="Alert" selected>- Choose Currency Unit Below -</option>
</select>

<script type="text/javascript">
for (var i = 1; i < currencyOptionData.length; i++) {
    document.getElementById("moneySelect1").options[i] = new Option(currencyOptionData[i][0], currencyOptionData[i][1]);
    document.getElementById("moneySelect2").options[i] = new Option(currencyOptionData[i][0], 1 / currencyOptionData[i][1]); }
</script>


User inputs and their validity

Numerical amounts only, please

The original Compute( ) function flags an empty MoneyFormIn field but otherwise allows all non-blank MoneyFormIn inputs to proceed to the Money conversion code. We of course want to intercept all non-numeric MoneyFormIn.values before we do any arithmetic, and an easy and fail-safe way to do that is to map such inputs onto NaN via the Number( ) function.

function Compute( ) {
    var moneyNumber = Number(document.getElementById("moneyInput1").value);
    if (! moneyNumber) window.alert("You must choose an amount to convert.");


NaN is a falsy value, i.e., it converts to false in a logical context.
• A negative number representing a loss is a legitimate input.
• A corresponding parseFloat(document.getElementById("moneyInput1").value); command would stop most non-numeric inputs but let something like 1234abcd get through, so go with Number( ).

Much more complicatedly, we can also use regular expressions to fend off unwanted MoneyValue strings:

var MoneyValue = document.getElementById("moneyInput1").value;
if (! /^[+-]?\d*(\.\d*)?$/.test(MoneyValue) || /^[+-]?\.$/.test(MoneyValue))
    window.alert("You must choose an amount to convert");


Alert selections are not OK

To check if the user has chosen a first currency and a second currency, Compute( ) gets the Unit and Unit2 selectedIndexes and then uses those indexes to get the selected options' values and then tests if those values are equal to Alert, the options[0].value for both menus. Needless to say, this is overkill: we really just need to test if the selectedIndexes themselves are equal to 0.

else if (! document.getElementById("moneySelect1").selectedIndex) /* 0 is also a falsy value. */
    window.alert("You must choose a first currency.");
else if (! document.getElementById("moneySelect2").selectedIndex)
    window.alert("You must choose a second currency.");


The Money Conversion Script predates the advent of the DOM Level 1 Specification and, as classical JavaScript's client-side Select object did not have a value property, CompuH@cker did not have the option of using if (selectObject.value == "Alert") tests here, although we could do that if we wanted to even as the selectedIndex tests (which CompuH@cker could have used) are IMO the simpler way to go.

No eval( ) please, we're multiplying

As noted in Blog Entry #379, Compute( ) typecasts the MoneyFormIn.value string and the options[selectedIndex].value strings to numbers for the conversion arithmetic via the eval( ) function: validation and security concerns aside, this is unnecessary because the input strings are used in multiplication operations for which string → number conversion occurs automatically. CompuH@cker could have written

var Money = MoneyValue * UnitValue * Unit2Value;

with no loss of functionality.

It stops at the hundredths place (or not)

The Money output is truncated and rounded at the hundredths place via a roundToPennies( ) function.

function roundToPennies(n) {
    pennies = n * 100;
    pennies = Math.round(pennies);
    strPennies = "" + pennies;
    len = strPennies.length;
    first = strPennies.substring(0, len - 2) + ".";
    last = strPennies.substring(len - 2, len);
    if (first == ".") { first = "0."; }
    if (last.length == 1) { last += "0"; }
    return first + last; }

document.forms[0].elements["MoneyFormOut"].value = roundToPennies(Money);


An analysis of the roundToPennies( ) function is left to the reader; my job is to tell you that we can replace it with a single numberObject.toFixed(2) command:

document.getElementById("moneyInput2").value = moneyNumber.toFixed(2);

• The toFixed( ) method of the Number object was implemented in JavaScript 1.5; CompuH@cker didn't have access to it.
• The toFixed(number) argument may be a [numeric] value between 0 and 20, inclusive, and implementations may optionally support a larger range of values, quoting Mozilla, so yes, you can go beyond the hundredths place if you want. (roundToPennies( ) can be reconfigured to go beyond the hundredths place but that takes a lot more work.)
• Calling toFixed( ) on a string (numeric or otherwise) throws a ... is not a function TypeError.

The format factor

The format-and-output statement appears twice in the post-validation else clause. We can tidy the clause up a bit by 'factoring out' the format-and-output statement and placing it after a UnitName != Unit2Name-type conditional that holds the arithmetic.

else {
    if (document.getElementById("moneySelect1").selectedIndex != document.getElementById("moneySelect2").selectedIndex)
        moneyNumber = moneyNumber * document.getElementById("moneySelect1").value * document.getElementById("moneySelect2").value;
    document.getElementById("moneyInput2").value = moneyNumber.toFixed(2); }


For that matter, you can lose the UnitName-vs.-Unit2Name test altogether if you don't feel the need to shield the UnitName == Unit2Name situation from the arithmetic.

Reset it

It may have occurred to you that the script interface lacks and needs a button. No form? No problem:

<button type="button" onclick="resetFields( );">Reset</button>

function resetFields( ) {
    document.getElementById("moneyInput1").value = "";
    document.getElementById("moneyInput2").value = "";
    document.getElementById("moneySelect1").selectedIndex = 0;
    document.getElementById("moneySelect2").selectedIndex = 0; }


Demo

At the moneyconv.html page, Joe Burns says:
Great script, but it requires some upkeep. You choose what you have up top -- then choose what you want down below. Click Compute and poof -- you get the conversion. That is, if the conversion numbers are correct. Hence, the upkeep. But if you can keep the numbers up, it's quite functional.
I provide in a div below a demo that incorporates the code presented in this entry and the <option>-al changes detailed in part one of this series* - check the source of the current page for the full demo coding. Here's where you come in:
(1) Add more currencyOptionData[i][0] currencies to the selection lists and specify up-to-date currencyOptionData[i][1] relative values for them.
(2) Update the currencyOptionData[i][1] relative values for the other currencyOptionData[i][0] currencies.
You're ready to give XE ("The World's Trusted Currency Authority") a run for the money, aren't you?
*I've left the pre-euro eurozone currencies (Austrian schilling, Belgian franc, etc.) in place as they can still be exchanged with the euro or any other current currency.

Money Converter






Tuesday, September 19, 2017
 
The Option Optic
Blog Entry #380

In casting about for a more efficient way to code the selection list data of the Money Conversion Script I came across a related Example from JavaScript 1.1-1.3 that is itself worthy of a brief post.

Two exceptional aspects of classical (pre-DOM) JavaScript are highlighted by the Example:
(1) A client-side Option object can be created via a new Option( ) constructor operation (classical JavaScript featured two constructible objects, Image being the other).
(2) Alone* among the various client-side array** properties (document.forms, window.frames, etc.), the Select.options array is writable.
We will apply these aspects to the Unit and Unit2 selection lists in the following entry.

*Nope, we can't create an HTML <img> element with
var myImage = new Image( );
myImage.src = "some_image.png";
document.images[0] = myImage;

and we can't create an HTML <a> element with
document.links[0] = "Link text".link(linkURL);,
or at least I can't on my computer.

**These "arrays" (Netscape's term) are not core Array objects in that
(a) excepting length you can't use the Array object's properties and methods with them and
(b) their childObject members can be referenced associatively (parentObject.childObjects["identifierString"]) as well as ordinally (parentObject.childObjects[indexNumber]).

Starting structure

The Example's HTML codes a select-one selection list and a select-multiple selection list that have no option children.

<h3>Select Option( ) constructor</h3>
<form>
<select name="selectTest"></select><p>
<input type="button" value="Populate Select List" onclick="populate(this.form);">
<p>
</form>
<hr>
<h3>Select-Multiple Option( ) constructor</h3>
<form>
<select name="selectTest" multiple></select><p>
<input type="button" value="Populate Select List" onclick="populate(this.form);">
</form>


Each selection list sits in a separate and unnamed form, is itself named selectTest, and is paired with a push button. Clicking that button calls a populate( ) function that is supposed to stock the selection list with a set of four options - and will, once we get rid of an offending line of code.

Build it and they will opt

The populate( ) call passes to populate( ) a this.form reference to the parent form, which is given an inForm identifier.

function populate(inForm) { ... }

The populate( ) body begins by defining a colorArray Array of color keyword strings; none of populate( )'s subsequent operations calls on colorArray, however.

colorArray = new Array("Red", "Blue", "Yellow", "Green");

Next, populate( ) constructs four Option objects for the four colorArray colors.

var option0 = new Option("Red", "color_red");
var option1 = new Option("Blue", "color_blue");
var option2 = new Option("Yellow", "color_yellow");
var option3 = new Option("Green", "color_green");


The first new Option( ) argument specifies the Option text; the second new Option( ) argument specifies the Option value. The Option objects are given the identifiers option0, option1, option2, and option3, respectively.

The option0-option3 objects are iteratively loaded into the empty inForm.selectTest.options array, a process that produces corresponding Red/Blue/Yellow/Green HTML option elements and appends them to the selectTest select element; the options[0] Red option is selected once the options are in place.

for (var i = 0; i < 4; i++) {
    eval("inForm.selectTest.options[i] = option" + i);
    if (i == 0) {
        inForm.selectTest.options[i].selected = true; } }


The populate( ) action concludes with a

history.go(0);

command that was required to render the option HTML with Netscape 3.x but clears the options with modern browsers and must now be removed for the Example to work as it should.

eval( ) exit

The eval( ) command that coordinates the selectTest optionization does what it is supposed to do but nonetheless isn't very ... elegant, shall we say - here are some more satisfying code possibilities:

(1) We can organize the new Option objects as a bona fide Array object and then write them as Array elements to the inForm.selectTest.options array.

var colorOptions = new Array( );
colorOptions[0] = new Option("Red", "color_red", false, true);
colorOptions[1] = new Option("Blue", "color_blue");
colorOptions[2] = new Option("Yellow", "color_yellow");
colorOptions[3] = new Option("Green", "color_green");
for (var i = 0; i < colorOptions.length; i++)
    inForm.selectTest.options[i] = colorOptions[i];


The new Option( ) constructor can take a third argument specifying the Option's defaultSelected status and a fourth argument specifying the Option's selected status. If we create the colorOptions discretely, then we can select the Red Option at the constructor stage, in which case a separate selected = true assignment is unnecessary.

(2) As shown earlier, the new Option texts are colorArrayed in the original code; if we similarly pre-Array the new Option values, then we can iteratively create the Options and write them as themselves to the inForm.selectTest.options array.

var colorOptionTexts = new Array("Red", "Blue", "Yellow", "Green");
var colorOptionValues = new Array("color_red", "color_blue", "color_yellow", "color_green");
for (var i = 0; i < colorOptionTexts.length; i++)
    inForm.selectTest.options[i] = new Option(colorOptionTexts[i], colorOptionValues[i]);
inForm.selectTest.options[0].selected = true;


The corresponding createElement( )/appendChild( ) route would require a few more lines of code:

for (var i = 0; i < colorOptionTexts.length; i++) {
    var colorOption = document.createElement("option");
    colorOption.text = colorOptionTexts[i];
    colorOption.value = colorOptionValues[i];
    inForm.selectTest.appendChild(colorOption); }


(3) We can pre-organize the new Option texts and values as a two-dimensional Array and then proceed à la (2) above.

var colorOptionData = new Array( );
colorOptionData[0] = ["Red", "color_red"];
colorOptionData[1] = ["Blue", "color_blue"];
colorOptionData[2] = ["Yellow", "color_yellow"];
colorOptionData[3] = ["Green", "color_green"];
for (var i = 0; i < colorOptionData.length; i++)
    inForm.selectTest.options[i] = new Option(colorOptionData[i][0], colorOptionData[i][1]);
inForm.selectTest.options[0].selected = true;


Mozilla has been warning us for a while that eval( ) is a dangerous function (specifically, eval( ) can enable a cross-site scripting attack depending on the UI) and is also generally slower than the alternatives; these considerations are unimportant vis-à-vis the Example, but given that loops and arrays are meant for each other, we really ought to be pressing Mr. Array into service here, yes?

Demo

I deploy the two-dimensional colorOptionData Array in the following demo; check the source of the current page for the full coding.

The bordered div below holds two empty selection lists. Click the buttons to populate the lists with options on the fly.

Select-One Option( ) Constructor




Select-Multiple Option( ) Constructor




Monday, September 04, 2017
 
Keeping Up with the Currencies
Blog Entry #379

In today's post we will discuss the Calendars, Clocks, and Calculators (CCC) sector's Money Conversion Script, which went live in November 1997 and was authored by "CompuH@cker". Per its label, the Money Conversion Script converts the number of units of a first currency to the corresponding number of units of a second currency.

The Money Conversion Script demo at the aforelinked moneyconv.html page works as advertised. To access the script's code, go to the JavaScript Goodies moneyconv.txt page: the corresponding page at Java Goodies is still available, but the code thereat is commingled with a bunch of Internet Archive-related stuff.

The script works with
(i) a set of world currencies that is nowhere close to being complete and
(ii) a set of relative currency values that is seriously out of date,
but that doesn't mean it can't serve as a starting point for the creation of a first-rate currency converter.

I/O structure

The user first enters a money number for a first currency into a MoneyFormIn text box.

<body>
<center>
<h1>Money Converter</h1>
<hr>
<form name="MoneyForm">
<input type="text" name="MoneyFormIn" size="20">


Next, the user selects a first currency from a Unit selection list containing 69 currency options

<select size="1" name="Unit">
<option value="Alert" selected>- Choose Currency Unit Below -
<option value="2900.0000">DZD Algerian Dinars
<option value="170000.00">USD American Dollars
...67 more currency options...
</select>


and then a second currency from a Unit2 selection list with a matching set of currency options.

• The absence of </option> tags is legit but I myself would put them in.
• For some reason I had it in my head that 1 is the default value of the select element's size attribute: not so, the default size is #IMPLIED.

Between the selection lists is a push button and a MoneyFormOut text box.

<hr><input type="button" value="Compute!" onclick="Compute( );">
<hr>
<input type="text" name="MoneyFormOut" size="20">
<select size="1" name="Unit2">
<option value="Alert" selected>- Choose Currency Unit Below -
<option value="0.0003500">DZD Algerian Dinars
<option value="0.0000060">USD American Dollars
...
</select></form>


Clicking the button calls a Compute( ) function that via some simple arithmetic transforms the MoneyFormIn number to the equivalent money number for the Unit2 currency and subsequently displays the new number in the MoneyFormOut field.

<option> commentary

The Unit and Unit2 menus include options for the Austrian schilling, the Belgian franc, the Cypriot pound, the Dutch guilder, the Finnish markka, the French franc, the German mark, the Greek drachma, the Irish punt, the Italian lira, the Luxembourg franc, the Portuguese escudo, the Slovakian koruna, and the Spanish peseta; however, Austria, Belgium, Cyprus, the Netherlands, Finland, France, Germany, Greece, Ireland, Italy, Luxembourg, Portugal, Slovakia, and Spain now all use the euro. Relatedly, the menus also include a "European Currency Units" option: the European currency unit was the precursor to the euro.

The name of each option currency is prefaced by an identifying three-letter (ISO 4217) currency code. Some of these codes need to be updated:
ARP ARS Argentinian Peso
BRR BRL Brazilian Real
BGL BGN Bulgarian Lev
CSK CZK Czech Koruna
MXP MXN Mexican Peso
PLZ PLN Polish Zloty
ROL RON Romanian Leu
SUR RUB Russian Ruble
TRL TRY Turkish Lira
VEB VEF Venezuelan Bolivar
ZMK ZMW Zambian Kwacha

More changes from yesteryear to today:
XEC Eastern Caribbean Units → XCD Eastern Caribbean Dollar
XAU Gold Ounces (New York) → XAU Troy Ounce of Gold
XPT Platinum Ounces (New York) → XPT Troy Ounce of Platinum
XAG Silver Ounces (New York) → XAG Troy Ounce of Silver
SDD Sudanese Dinar → SDG Sudanese Pound
and of course
XEU European Currency Units → EUR Euro

Now, how 'bout them values? The Unit and Unit2 menus both use as a reference point the value of the Turkish lira ("lira" hereafter, given that Italy uses the euro), which was ranked as the world's least valuable currency in the mid-1990s. Specifically:
(1) For the Unit menu, each currency option value gives the value of the option currency relative to that of the lira, e.g., one Algerian dinar is 2900.0000 times more valuable than one lira.
(2) For the Unit2 menu, each currency option value gives the value of the lira relative to that of the option currency, e.g., one lira is 0.0003500 times as valuable as one Algerian dinar.
(My use of the present tense in the preceding sentences is a stylistic preference and is not meant to imply that the script's relative currency values are still correct.)
Each Unit2 currency option value is thus the reciprocal of its corresponding Unit currency option value.

As to where all those values came from, you'd have to ask CompuH@cker about that.

It's more fun to Compute( )

Input reading

The Compute( ) function first reads the MoneyFormIn value and assigns it to a MoneyValue variable.

function Compute( ) {
    MoneyValue = document.forms["MoneyForm"].elements["MoneyFormIn"].value;


After that Compute( ) gets the selectedIndexes of the Unit and Unit2 menus and respectively gives them UnitPlace and Unit2Place identifiers.

UnitPlace = document.forms["MoneyForm"].elements["Unit"].selectedIndex;
Unit2Place = document.forms["MoneyForm"].elements["Unit2"].selectedIndex;


UnitPlace and Unit2Place are subsequently plugged into the menus' options[ ] collections so as to get the selected options' values, which are dubbed UnitValue and Unit2Value, and texts, which are dubbed UnitName and Unit2Name, respectively.

UnitValue = document.forms["MoneyForm"].elements["Unit"].options[UnitPlace].value;
Unit2Value = document.forms["MoneyForm"].elements["Unit2"].options[Unit2Place].value;
UnitName = document.forms["MoneyForm"].elements["Unit"].options[UnitPlace].text;
Unit2Name = document.forms["MoneyForm"].elements["Unit2"].options[Unit2Place].text;


Validation

With the MoneyValue, UnitValue, and Unit2Value values in hand, Compute( ) displays a relevant alert( ) message if the user has left the MoneyFormIn field blank or has not selected a first or second currency.

if (MoneyValue == "")
    window.alert("You must choose an amount to convert.");
else if (UnitValue == "Alert")
    window.alert("You must choose a first currency.");
else if (Unit2Value == "Alert")
    window.alert("You must choose a second currency.");


Oh, and what if the user enters Hello World into the MoneyFormIn field? I'll show you an easy way to deal with other non-numeric MoneyValues later.

Money conversion

The validation if...else if...else if cascade is followed by a concluding else clause that gets down to the brass tacks of mapping the MoneyValue value to its value in the second currency. The MoneyValue numeric string is first type-converted to a Money number via the top-level eval( ) function.

else { Money = eval(MoneyValue);

If for whatever reason the user selects the same currency from the Unit and Unit2 menus, then Money is whisked to a roundToPennies( ) function that truncates and rounds a relevant floating-point number at the hundredths place (e.g., 2.34562.35); the roundToPennies( ) return is written to the MoneyFormOut field.

if (UnitName == Unit2Name)
    document.forms["0"].elements["MoneyFormOut"].value = roundToPennies(Money);
/* The 0 forms[ ] index doesn't need to be stringified. */


We'll address the roundToPennies( ) function - and its Number.toFixed( ) equivalent - in our next episode, but for now let's keep going. If the first and second currencies are different,
then the UnitValue and Unit2Value numeric strings are respectively type-converted to ToTRL and FromTRL numbers

else {
    ToTRL = eval(UnitValue);
    FromTRL = eval(Unit2Value);


and then Money is multiplied by ToTRL so as to give a TRL number of lira

TRL = Money * ToTRL;

and then TRL is multiplied by FromTRL so as to give a Money amount of the second currency

Money = TRL * FromTRL;

and finally the new Money number is formatted by the roundToPennies( ) function and the roundToPennies( ) return is written to the MoneyFormOut field à la the preceding if clause.

document.forms["0"].elements["MoneyFormOut"].value = roundToPennies(Money); } } } /* That's it for Compute( ). */
I'll put forward an alternative coding for the Money Conversion Script in the following entry.


Powered by Blogger

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