Monday, December 09, 2019
More Tuition Check
Blog Entry #404
Let's get back now to our ongoing analysis of the JavaScript/Java Goodies College Tuition Chooser and its
calc( )
/validateData( )
/pow( )
input-processing functionality.Original processing issues, in execution order
The College Tuition Chooser was written during the JavaScript 1.2 → Navigator 4.0-4.05 era and the
calc( )
and validateData( )
functions both feature an if (agentInfo == "Netscape 4.0") { ... }
block. No features in those blocks were implemented in JavaScript 1.2 or are Netscape-specific, however. Upon switching the agentInfo == "Netscape 4.0"
conditions to agentInfo != "Netscape 4.0"
conditions, Navigator 3.04 and IE 4.5 run the blocks and then the computeForm( )
function without incident. Even Navigator 2.0, the first browser with a JavaScript engine, smoothly handles the validateData( )
for-Netscape 4.0 code although this version does throw a value is not a numeric literal error at the computeForm( )
stage, which we will discuss later.Regarding non-digit ch characters, the
validateData( )
for-Netscape 4.0 block allows the presence of decimal points and commas and $s whereas the following not-for-Netscape 4.0 else { ... }
block allows the presence of only decimal points: this is fine for our $12,34r.67 example but we'll have to up our game if we want to stop inputs like ..1.23 and ,45,,6,,, and 7$8$9.Every non-digit character in the initial input.value string is removed by the
validateData( )
for-Netscape 4.0 block, which is fine for a correctly placed $ or comma or decimal point but definitely causes a problem vis-à-vis the $12,34r.67 value's mistyped r, whose removal prevents the integer digits from reaching their original place-value positions - the 1 stops at the thousands place versus the ten-thousands place, the 2 stops at the hundreds place versus the thousands place, etc. - so that the validateData( )
correctedNum return, 1234.67, is smaller than the starting input by a factor of 10! We can minimize the damage in this case by mapping the r to a 0:else if ((ch != ",") && (ch != "$")) {
window.alert("The invalid character " + ch + " was detected and will be replaced by a 0.");
if (! isDecimal) correctedNum = Number(correctedNum + "0");
else { addOn += "0"; ++decimalPlace; } }
/* The Number( ) string-to-number conversion works for Netscape 3+; for Netscape 2, eval( ) would work. */
In contrast, the not-for-Netscape 4.0 code converts the initial input.value to 0 when it hits a not-OKed ch character, which would be fair enough for a hello world input but a heavy-handed response for a 1,000 input - I prefer in this case to return focus( ) to the input and to leave in place and select( ) the value:
/* At the end of the calc( ) function, after validateData( ) has returned false: */
else {
window.alert("Your input may contain digits and a decimal point: please remove any other characters.");
window.setTimeout(function ( ) { input.focus( ); input.select( ); }, 100); }
/* The loss-of-focus aspect of the onchange="calc(this);" trigger can interfere with the focus( ) and select( ) operations but that interference can be overcome by a short setTimeout( ) delay. */
The
pow( )
function generates a 10decimalPlace power of 10 for the division operation that fractionizes the addOn part of the input; however, there's no need to create an ad hoc function when we can avail ourselves of the pow( )
method of the Math object for this purpose:if (decimalPlace > 0) correctedNum += addOn / Math.pow(10, decimalPlace);
The
validateData( )
/pow( )
code places no limits on the number of post-decimal point digits: we could input 3.141592653589793 and the whole thing would be carried forward. However, it is simple enough to truncate such a too_long str string at the 'thousandths' place and penultimately round the final correctedNum number at the hundredths place:var str = theNum;
var decimalpointindex = str.indexOf(".");
if (decimalpointindex != -1 && decimalpointindex + 2 < str.length) {
var too_long = true;
str = str.substring(0, str.indexOf(".") + 4); }
...
if (too_long) correctedNum = Math.round(correctedNum * 100) / 100;
// More modernly: if (too_long) correctedNum = correctedNum.toFixed(2);
return correctedNum;
One nonetheless wonders, "Why have different ways of dealing with the integer and fractional parts of an input? Just recast the str string as a new well-formed numeric str2 string containing only digits and a decimal point, and then
Number( )
-ize str2 at the end. What's the problem?"Segue to regular expressions
If we do want to do something special for level 4 (and later) browsers versus their predecessors, then we can vet the cost inputs via a suitable regular expression: regular expressions were implemented in JavaScript 1.2.
// Begin the calc( ) body with:
var cost_pattern = /^\$?(\d{1,6}|\d{1,3},\d{3})(\.\d{0,2})?$/;
if (cost_pattern.test(input.value)) { ... computeForm(input.form); }
else {
window.alert("Houston, we have a problem - please get the offending character(s) out of your input.");
window.setTimeout(function ( ) { input.focus( ); input.select( ); }, 100);
return; }
The cost_pattern pattern allows the presence of
a starting $ and
digits running from the hundred-thousands place* to the hundredths place and
a comma between a thousands place digit and a hundreds place digit and
a decimal point between the ones place digit and a tenths place digit
in the input.value.
Everything else ist verboten, ja?
*The annual tuition cost for some dental schools clears six figures.
For arithmetic purposes the inputs cannot contain a $ or a comma, but we can easily track down these characters and
replace( )
them with empty strings via the following lines of regular expression-based code:var charOK = /[$,]/g;
input.value = input.value.replace(charOK, "");
We presented a similar approach to the removal of a document's HTML tags in the A non-iterative 'detagification' section of Blog Entry #98.
Other code possibilities that prevent Netscape 2.x-3.x errors, in the name of completeness
Navigator 3.04 and Navigator 2.0 throw a syntax error as soon as they hit the
var cost_pattern = /^\$?(\d{1,6}|\d{1,3},\d{3})(\.\d{0,2})?$/;
line,even if that line is in the body of a not-called function or in a not-operative if or else block: we can suppress this error if we formulate the cost_pattern pattern via the RegExp object constructor
var cost_pattern2 = new RegExp("^\\$?(\\d{1,6}|\\d{1,3},\\d{3})(\\.\\d{0,2})?$");
and shield the regular expression code with an
if (input.value.replace) { ... }
gate.Like regular expressions themselves, the
replace( )
method of the String object was implemented in JavaScript 1.2.I should also note that Navigator 3.04 and Navigator 2.0 similarly throw a syntax error as soon as they hit the
window.setTimeout(function ( ) { input.focus( ); input.select( ); }, 100);
line:we can suppress this error if we
define a global
var input2 = null;
variable,assign
input2 = input;
in the calc( )
body, andrecast the
focus( )
/select( )
action as window.setTimeout("input2.focus( ); input2.select( );", 100);
.Mozilla now discommends the
setTimeout(codeString, delay)
syntax, but again, we are accommodating late-1990s Netscape 2.x-3.x users here. I have no idea why the calc( )
and setTimeout( )
execution contexts converge in one case and diverge in the other.Other inputs
For previous CCC applications we have generally intercepted blank, 0, and negative number Text.values.
A negative number is converted to its additive inverse by the for-Netscape 4.0 part of the
validateData( )
function but is blocked - as it should be - by the not-for-Netscape 4.0 part and by my if (cost_pattern.test(input.value))
gate.A 0 input is of course OK; a blank input is a 0-equivalent in an arithmetic context and is also OK, as it would be for a standard spreadsheet. The
onchange="calc(this);"
event handler is not triggered by leaving a field blank in the first place, although it would be triggered if we were to clear a non-blank field and then blur the field. The original chooser code handles the latter case satisfactorily:(NN4.0) With Netscape 4.0x browsers, the now-empty input.value is mapped to 0 by the relevant parts of the
validateData( )
and calc( )
functions, and we actually get a 0 in the field on the page when the dust has settled.(!NN4.0) With non-Netscape 4.0x browsers, the input.value passes through the relevant parts of the
calc( )
and validateData( )
functions without modification and is in almost all cases subsequently converted to 0 at the level of code but not on the page by the computeForm( )
function.An empty input.value would be blocked by the
if (cost_pattern.test(input.value))
gate: adding an || ! input.value.length
subcondition to the gate will let it through.
We'll check over and retool the chooser's
computeForm( )
and formClear( )
functions in the following entry.Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)