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