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

Comments: Post a Comment

<< Home

Powered by Blogger

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