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

Sunday, August 20, 2017
 
Everything Counts Regardless of Amount
Blog Entry #378

BurnBlade's Digital Clock script is preceded by a Number of Letters script, which was authored by Greg Bland. The Number of Letters script
(a) counts the characters of an <input type="text"> box's value and then
(b) displays the count on an alert( ) box.

The Number of Letters script's numbletters.txt code page at Java Goodies is gone although you can still get the code at the corresponding JavaScript Goodies page. The aforelinked numbletters.html script page features a demo that does what it is supposed to do but also includes some strangeness that we will sort out below.

HTML interface

At the numbletters.html page the user is greeted by a theform form that holds two controls:
(1) a words text box and
(2) a push button.

<body>
<form name="theform">
<input type="text" name = "words" value = "The most letters this can do is 255" size = "40"></textarea>
<input type="button" value="Check"onclick="check( );">
</form>


• SGML-wise - per the STag, Attribute, and Eq productions in the XML 1.0 specification - the spaces flanking the = character for the <input type="text">'s name, value, and size attributes are legit but the absence of a space between the <input type="button">'s value and onclick attributes is not, although the latter doesn't cause any problems with the browsers on my computer.

The user does or does not enter some new text into the words box and then clicks the button, thereby triggering a check( ) function, which we'll get to shortly.

The "Number of Letters" description notwithstanding, the user's input can comprise alphabetic letters, numerals, symbols, and/or spaces.

The words box's initial value, The most letters this can do is 255, implies that the user can input up to 255 characters but not more than that. I don't know where the 255 number comes from. In theory, the default words maxlength is an unlimited number; in practice, inputting millions of characters may cause your browser to hang.

As you can see, there's a stray </textarea> tag up there; if you are going to enter a lot of stuff, then a textarea field would indeed be a better choice.

Check it

The check( ) function begins unproductively:

<script language="javascript">
/* ...Authorship/copyright notice... */
function check( ) {
    var l = document.theform.words.length;


The HTML DOM's HTMLInputElement interface doesn't have a length attribute, and l accordingly returns undefined; none of check( )'s subsequent operations calls on l, however.

Next we have the one part of check( ) that makes sense:

window.alert("You typed in " + document.theform.words.value.length + " letter(s).");

String objects do have a character-counting length property, and the document.theform.words.value string's length gives us the number we want, which with a bit of additional text is outputted to the user. If you don't touch the words field and click the button, you'll get the The most letters this can do is 255 length, 35; if you clear the The most letters this can do is 255 string and don't replace it with anything and click the button, you'll get 0.

The check( ) function concludes with two purposeless conditionals

if (document.theform.words.value.length != 1) {
    window.alert("Invalid number of letters, try 1 letter."); }
if (document.theform.words.value.length == 1) {
    window.alert("You learn fast!"); } }
</script>


that output messages if the user's input is not just one character and is one character, respectively. (Perhaps Greg's 'purpose' was to annoy the user, but we really shouldn't be doing that, should we?)

Keep the document.theform.words.value.length reading, chuck the rest.

wc it

With some help from our good friend Mr. Regular Expression, we can easily adapt the script to counting the words and line breaks in the input à la the Unix wc command.

Words

If we broadly define a "word" as a string of characters containing at least one vowel and no white space, then we can get the number of words in the words value via the following code:

var re_spaces = /\s+/;
var re_vowel = /[aeiouy]/i;
var word_count = 0;
var input_words = document.theform.words.value.split(re_spaces);
for (var i = 0; i < input_words.length; i++)
    if (re_vowel.test(input_words[i])) word_count++;
window.alert("Number of words: " + word_count);


The re_spaces regexp pattern matches one or more white space characters; the re_vowel regexp pattern matches a single case-insensitive ASCII vowel character. We split( ) the words value at each re_spaces separation to give an array of input_words, which are then iteratively test( )ed against the re_vowel pattern; each positive (true) test increments a word_count.

Line breaks

Getting the number of line breaks is even easier:

var re_endofline = /(\r\n|\n|\r)/g;
var linebreak_array = document.theform.words.value.match(re_endofline);
var linebreak_count = linebreak_array ? linebreak_array.length : 0;
window.alert("Number of line breaks: " + linebreak_count);


The re_endofline regexp pattern matches three types of line endings:
(1) \r\n, for the Windows platform;
(2) \n, for Unix-based platforms; and
(3) \r, for the 'classic' (pre-OS X) Macintosh platform.
The pattern's g flag enables us to match each occurrence of an ending throughout the input; we can retrieve those endings as an array via a match( ) operation.

If there are no line breaks in the input, then the linebreak_array is null, in which case the linebreak_count is set to 0.

Everything but the white space sink

The regular expression-interpreting submodule of the browser's JavaScript engine does not treat non-ASCII vowels (e.g., å, ê, ì) and symbols as word characters but wc itself certainly does, so if you want to be able to pick up a word like für (maybe you've just learned how to play "Für Elise" on the piano) and you don't mind picking up 'words' like 255 and $$$ (wc would see them as words), then you can do that with:

var re_nonspaces = /\S+/g; /* \S matches any non-white space character. */
var word_array = document.theform.words.value.match(re_nonspaces);
var word_count = word_array ? word_array.length : 0;
window.alert("Number of words: " + word_count);


Demo

Time for a demo, eh? Enter whatever you like into the textarea field below and then click the button to get the number of line breaks, words, and characters (including white space characters) in your input. A button is provided for your convenience.




Check the source of the current page for the full coding.
We'll take up the CCC sector's Money Conversion Script in the following entry.

Tuesday, August 01, 2017
 
Tic Tic Tic It Holds On
Blog Entry #377

Today's post marks our return to the Calendars, Clocks, and Calculators (CCC) sector of the Java Goodies JavaScript Repository. Continuing in reverse chronological order, the Another Great Science Calculator script is preceded by a Digital Clock script that was authored by "BurnBlade" at some point in 1997. I've lost track of how many clock scripts we've heretofore covered - "Too many," you are perhaps thinking - but because I hold that every script deserves its fifteen minutes of fame, we will give BurnBlade's script a look-over in the discussion below.

Overview/layout

The Digital Clock script's code can be viewed here and its effect is displayed at the aforelinked digitalclock.html page. The display comprises three parts:
(1) an hour:minute:second time part and
(2) an a.m. or p.m. part
that compose a 12-hour clock, and
(3) a month/date/year date part.
The script's claim to distinction is its layout, which positions the display parts in separate cells of a two-row <table>.

<form name="timedate" onsubmit="0">
<table cellspacing="0" cellpadding="0">
<tr align="center">
<td><input type="text" name="time" size="7" value="..Starti"></td>
<td><input type="text" name="parts" size="2" value="ng.."></td></tr>
<tr align="center">
<td colspan="2"><input type="text" name="date" size="8" value="........"></td></tr>
</table></form>


The use of form <input> parking spaces for the parts reflects (vide infra) the JavaScript state of the art in 1997. There are no real red flags here* but we will use a simpler structure in the demo at the end of the post.

*Admittedly, the onsubmit="0" event handler is kind of weird and doesn't serve any useful purpose, but it's not invalid, either. BTW, HTML 3.2, the current version of HTML when the script was written, qualifiedly green-lights the use of tables for layout purposes.

The display is horizontally centered by a <center> element, which is strangely (and invalidly, note the <body> markup) wrapped around all of the rest of the code:

<center><script language="javascript">
...JavaScript statements, functions, and commands...
</script>
<body onload="startwatch( );">
<form name="timedate" onsubmit="0">
...
</form></center>


Putting the center element just around the table element and its descendants is what we would have wanted to do:

<form name="timedate" onsubmit="0"><center><table cellspacing="0" cellpadding="0">...Rows and cells...</table></center></form>

Part production

The time and a.m. or p.m. parts of the display are produced by the following JavaScript:

day = new Date( );
hour = day.getHours( );
minute = day.getMinutes( );
second = day.getSeconds( );
if (hour > 12) {
    hours = hour - 12;
    part = "PM"; }
else {
    part = "AM";
    if (hour == 0) {
        hours = 12; }
    else {
        hours = hour; } }
if (minute < 10) { minutes = 0; }
else { minutes = ""; }
if (second < 10) { seconds = 0; }
else { seconds = ""; }
time = ("" + hours + ":" + minutes + "" + minute + ":" + seconds + "" + second + "");
parts = ("" + part + "");
document.timedate.time.value = time;
document.timedate.parts.value = parts;


0s are prepended to minute and second values in the 0-9 range.
• Note that the 12 noon → 1 in the afternoon hour gets an AM designation.

Can we tighten up the above code? Yes indeed:

var day = new Date( ); var hour = day.getHours( ); var minute = day.getMinutes( ); var second = day.getSeconds( );
var part = hour > 11 ? "PM" : "AM"; /* The ?: conditional operator is detailed here. */
if (hour == 0) hour = 12;
if (hour > 12) hour -= 12;
if (minute < 10) minute = "0" + minute;
if (second < 10) second = "0" + second;
var time = hour + ":" + minute + ":" + second;
document.timedate.time.value = time;
document.timedate.parts.value = part;


The date part of the display is produced by:

date = ("" + (day.getMonth( ) + 1) + "/" + day.getDate( ) + "/" + day.getYear( ) + "");
document.timedate.date.value = date;


At the demo page, the year value is 117 - a smoking gun that the script uses day's getYear( ) return versus its getFullYear( ) return therefor. Given that + operations have a left-to-right associativity, the date calculation can be recast as:

var date = day.getMonth( ) + 1 + "/" + day.getDate( ) + "/" + day.getFullYear( );

Setting the clock in motion

Loading the parent document calls a startwatch( ) function that itself calls
(a) a stopwatch( ) function, which could be used to stop the clock once it's running but doesn't do anything at this juncture (OK, it resets the watchRun flag to false), and then
(b) a dayTime( ) function that produces an initial clock reading;
dayTime( ) is re-called every 1000 milliseconds so as to update the clock.

var watchID = null;
var watchRun = false;
function stopwatch( ) {
    if (watchRun) window.clearTimeout(watchID);
    watchRun = false; }
function startwatch( ) {
    stopwatch( );
    dayTime( ); }
function dayTime( ) {
    ...Determination and display of the time, parts, and date values as detailed above...
    watchID = window.setTimeout("dayTime( );", 1000);
    watchRun = true; }
...
<body onload="startwatch( );">


If you don't care about stopping the clock (the code contains no mechanism to do so, although it is simple enough to append an onclick="stopwatch( );"-triggering button to the timedate form), then you can ditch the startwatch( ) and stopwatch( ) functions and just use a window.onload = dayTime; statement to get things under way.

Don't render me, Bro

Like many old scripts, the Digital Clock JavaScript begins and ends with some "Hiding Scripts Within Comment Tags" HTML markup:

<script language="javascript">
<!-- Begin Digital Watch
...
// End Digital Watch-->
</script>


Such hiding code has been anachronistic for quite some time although it made sense for BurnBlade to have included it back in the day. However, the <!-- Begin Digital Watch line is followed by a
document.write("<!-- Begin Hiding Script -->"); command
and the // End Digital Watch--> line is preceded by a
document.write("<!-- End Hiding Script -->"); command.

I don't quite know what to make of those document.write( ) commands. Each write( ) command writes out an intact HTML comment; the text of those comments - Begin Hiding Script and End Hiding Script - implies that the commands are also meant to serve a hiding purpose even though the commands would not effect-wise duplicate the HTML hiding code for a non-JavaScript-capable browser. Moreover, the commands strike me as somewhat nonsensical in that the write( ) method is for writing something renderable to the page whereas comments are not meant to be rendered - I'd say we should throw them out, wouldn't you?

Demo + changes

We conclude with an updated demo.



I've traded in the form and table for a <div id="clockDiv" style="text-align:center;"> and a trio of <span>s.

var clockSpans = document.getElementById("clockDiv").getElementsByTagName("span");
clockSpans[0].textContent = time;
clockSpans[1].textContent = part;
clockSpans[2].textContent = date;


Check the source of the current page for the full coding.
We'll do some letter counting in the next post via the CCC Number of Letters script.

Thursday, June 15, 2017
 
Lightbox IX
Blog Entry #376

(Ophidiophobes may want to steer clear of this one.)

In today's post we will resurrect the missing demo of HTML Goodies' "Web Developer Class: How to Use the JavaScript Lightbox Image Viewer" tutorial.

The lightbox image viewer tutorial went live in May 2010. At that time, author Scott Clark was part of a reptile rescue collective called Reptile Clan Rescue of Florida. Scott demonstrated the lightbox effect with some photos of snakes that he and his confrères had rescued; the demo was located at http://www.reptileclan.com/lightbox/example.html and displayed at the HTML Goodies site via an iframe.

Evidently, Reptile Clan Rescue disbanded in early 2012, and its www.reptileclan.com Web site went with it. The HTML Goodies lightbox demo iframe now sports an ad selling the ReptileClan.com domain for $2095. It feels like déjà vu all over again when I look at that ad: naturally, I think of the home.earthlink.net/~reptile7jr/ demos I lost upon cutting ties with EarthLink.

We discussed the lightbox image viewer in Blog Entries #236, #237, #238, #239, #240, #241, #242, and #243. In studying the tutorial demo in preparation for those entries, I downloaded the demo images as a matter of course and am therefore in a position to restage the demo, so let's do that now, shall we? Click on a thumbnail photo or on the Me and a Huge Snake link below:

Snake #1Snake #2Me and a Huge Snake

New demo notes

• All of the demo's code is in the source of the current page. All styles are set either via inline style attributes or JavaScriptically rather than in an external lightbox.css file. The demo JavaScript has been placed in a <script> that immediately follows the <div> frame (vide infra) rather than in an external lightbox.js file.

• Scott laid out his demo via a <table> and pushed the lightbox links apart with non-breaking spaces (&nbsp;s). I've replaced the table and the iframe viewport with a div and separated the links via position:relative;left:20px; stylings.

• Per the showLightbox( ) registration section of Blog Entry #237, I've changed the lightbox links' rel="lightbox" identifier to a class="lightboxLink" identifier.

• Per the HTML interface section of Blog Entry #236, the initial display does not make use of the original baby_full_belly_thumb.jpg and punk_the_burmese_thumb.jpg thumbnails but instead scales down the full-size baby_full_belly.jpg and punk_the_burmese.jpg photos.

• Contra the z-index follies section of Blog Entry #238, the overlay and lightbox divs are given a style.zIndex = "1" (thereby shifting them to layer 7 of the root stacking context) so that they are painted over relatively positioned elements that come up later in the source (and are in layer 6 of the root stacking context).

• The central Punk python photo had a Baby the Albino Burmese Python caption that should have been attached to the left-hand Baby python photo; I've shifted the Baby... caption to Baby and given Punk his own caption.
We'll check over the Java Goodies Digital Clock (12/27/97) script in the following entry.

Monday, June 05, 2017
 
Misplaced Scripts and Links
Blog Entry #375

So, each and every one of my <iframe>-framed demos is back on track: the 'Mischief the talking cat' image map demo of Blog Entry #124, the falling hearts of snow demo of Blog Entry #270, the shopping cart demo of Blog Entry #302, they're all there. Also, I have now embedded in their respective posts the pre-Blog Entry #59 off-site demos that I formerly linked to but did not display, e.g., the image properties demo of Blog Entry #15. Rendering-wise, my retooled demos give you just what the original ones gave you in the overwhelming majority of cases, the odd difference notwithstanding.

One thing you generally won't get with my new demos is a "Check the page source for the full coding"-type sentence, and there's a reason for that: I cannot in good faith recommend my retooled demo code in that it necessarily commingles structure, presentation, and behavior, which are best kept separate from one another; be that as it may, there's nothing stopping you from extracting that code and putting it under the microscope yourself, and you are in fact welcome to do just that.

Hmmm, let me see, what else did I restore? Oh yeah...

Iframe scripts

For about 30 posts I had placed the 'script du jour' in an iframe.

<iframe width="85%" height="250" src="http://home.earthlink.net/~reptile7jr/jumpfocusscript.html" frameborder="1" scrolling="yes">If your browser does not support iframes, you may access the script by following the preceding link to the jumpfocuscode.html page.</iframe>

• This mode of display was used with longer scripts, for which the iframe scrolling value was either left at its auto default or set to yes.
• The script code was wrapped in a <pre> element so as to preserve its newlines, empty line boxes, and indentation.
• The iframe width was sometimes set to ≤ 85% to prevent the iframe from bumping into the "About Me" (id="main") div in the upper-right part of the page.

These scripts similarly went up in smoke upon cutting ties with EarthLink; fortunately, exchanging their iframe frames for analogous divs

<div style="width:73%;height:250px;border:inset 1px;overflow:auto;padding:0px 8px;"> ... </div>

went smoothly.

• The overflow:auto; declaration equips the div with scrollbars if they are needed.
• The div's padding-top and padding-bottom are kept at 0px (their initial value) and its padding-left is set to 8px as the pre element has an intrinsic vspace but doesn't have any hspace.

My approach to presenting code is reasonably settled at this point. I began marking it up with the <code> element and coloring it teal in the wake of going from a Classic Mac to an Intel Mac in late 2008; a little over a year later I began indenting block statements (the bodies of functions, if...else conditionals, etc.) by a tab although I now prefer a 'half-tab' (four monospace spaces) for this purpose. I don't display an entire script in one go anymore unless it's a short one.

Dead links

It wasn't my intention to repair the dead links in my posts at the outset of all of this. Encountering dead links is part and parcel of being a Web surfer, after all; moreover, if you've got what it takes to slog through my posts, then you should be up to the task of tracking down alternative citations as needs be. However, about halfway through my efforts a set of triggers spurred me to start repairing them:
(a) I felt a need to have a live link to the Netscape example that underpins the animation demo of Blog Entry #147.
(b) Upon reaching those posts dealing with HTML Goodies' "Opening New Windows With JavaScript" tutorials I discovered that the tutorials' portal page is gone.
(c) I relatedly discovered that the current HTML Goodies Beyond HTML : JavaScript sector bears no resemblance to the Beyond HTML : JavaScript sector I had been working through.
(d) Lastly, I was getting sick and tired of running into Forbidden links to sections in the JavaScript 1.3 Client-Side Guide and Reference.

So from circa Blog Entry #160 onward, most or all of my links will now take you to where you are supposed to go. Also, the links in Blog Entries #12-32 (excepting my entry on Hurricane Katrina) are now all live - I cleaned them up in the course of restoring my pre-Blog Entry #59 demos. Some of my new links point to current resources although many of them point to archived copies of their original targets. There are still plenty of dead links in my other older entries and I may go after them at a future point.
We are finally ready to pick up from where we left off last year and get back to the Java Goodies Calendars, Clocks, and Calculators sector for at least a little while longer. Before doing that, however, we will in the next entry take care of some unfinished business by restaging the demo that used to be in HTML Goodies' "How To Use the JavaScript Lightbox Image Viewer" tutorial.


Powered by Blogger

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