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







Powered by Blogger

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