reptile7's JavaScript blog
Friday, July 24, 2020
 
A Whole or Fractional power( )
Blog Entry #411

We return now to our run through the Super Calculator's exponentiation landscape. With all that prompt( )ing in the power( ), squared( ), and cubed( ) functions, it occurred to me that we should talk about the a and b data types a bit before we go any further...

xy input audit

For our 97 exponentiation in the previous post, the a = 9 and b = 7 inputs actually have a string data type: at Math.pow(a, b) time, the 9 and 7 operands are converted to numbers.

"" and null

If we leave a prompt( ) inputDefault field blank

Enter an exponent:

and click the button, then the prompt( ) output is the empty string; if we click the button, then the output is null (the primitive value vs. as a string). When plugged into a Math.pow( ) command, "" and null both convert to 0. FYI: Math.pow(0, 0) returns 1 even though 00 has no agreed-upon value.

Non-empty, non-numeric

Suppose we type hello in the total field (the total field is not readonly, we can do that) and try to raise it to the world power: as you would expect, helloworld returns NaN. Such inputs can be intercepted via an iterative isNaN( ) test (admittedly at the risk of annoying the user), e.g.:

b = window.prompt("Enter an exponent:", "");
while (isNaN(b)) { b = window.prompt("Your exponent must be a number:", ""); }


π

In the C/d section of Blog Entry #409 we learned that the SC's key outputs a Math.PI.toFixed(15) value surrounded by parentheses. The ( and ) characters prevent the conversion of the (3.141592653589793) string to a number for an exponentiation (or trigonometric) operation; as a result, Math.pow("(3.141592653589793)", b) returns NaN for all b except b = 0, for which the return is 1. We can easily lose the ( ) by pressing the key, i.e., eval("(3.141592653589793)") returns 3.141592653589793 (as a number, which is stringified when written to the total field), after which we can raise the pi value to whatever power we want.

Root extraction

Root extraction can be expressed exponentially: finding the yth root of some number x is equivalent to raising x to the 1/y power.

We can use the original power( ) function to calculate a x1/y root as long as the 1/y exponent is in decimal form: b = 0.5 for a square root, b = 0.25 for a 4th root, etc. As you would intuit from the preceding (π) discussion, a 1/y string's / character prevents its Math.pow( ) numberification.

If we don't know 1/y's decimal equivalent, then we can divide 1 by y in advance, copy the quotient to the clipboard, and paste the quotient into the Enter an exponent: inputDefault field when the time comes. As it happens, however, any numerator/denominator-type fraction can be eval( )ed to its decimal equivalent - cf. the SC's calc( ) function - so if we insert an
if (/^1\/\d+$/.test(b)) b = eval(b); statement* after each
b = window.prompt("Enter an exponent:", ""); statement,
then we can input 1/y itself into the inputDefault field.
*The /^1\/\d+$/ regexp pattern matches a 1/y string whose y character is an unsigned integer; we'd need a more complex pattern to additionally accommodate other ys.

So now, if we want to find the 7th root of 9 (maybe you would know that 1/7 = 0.14285714285714285, but I wouldn't), we're all set.

My Casio fx-85v calculator has an x1/y function, which is accessed via SHIFT-ing the key. We have the option of putting a image on a push button:

<button type="button" onclick=""><img width="" height="" style="" src="pathname/yth_root_of_x2.gif" alt="" /></button>

In creating the yth_root_of_x2.gif image, I gave the x and y a 16px/13px default/<sup> font-size ratio.

You're so square root, baby

The SC's Misc section has a dedicated button

<input type="button" value="Sqrt" onclick="getinput(squareroot);">

for getting the square root of a number. The button is bound to a squareroot( ) function that is called from the getinput( ) function à la the power( ), squared( ), and cubed( ) functions.

if (func == squareroot) { return squareroot(mode, a); }

The squareroot( ) function is detailed below; Mozilla's current Math.sqrt( ) page is here.

function squareroot(mode, obj) {
    if (mode == 1) { window.alert("This function gives you the square root of any given number"); }
    if (obj.value != "" && obj.value != "0") {
        aa = window.prompt("Do you want to find the square root of the current number in the total text box?", "y or n");
        if (aa == "y") { doit(Math.sqrt(obj.value), obj); }
        else {
            a = window.prompt("Enter a number to find the square root of:", "");
            more("(" + Math.sqrt(a) + ")", obj); } }
    else {
        a = window.prompt("Enter a number to find the square root of:", "");
        doit(Math.sqrt(a), obj); } }


I gave you a detailed power( ) deconstruction earlier, do we need to do the same thing here? Nah, let's go with a quick post-mode rundown:
(1) If the total.value isn't empty or 0 - if we've entered something into the total field, not necessarily a number - then we are prompt( )ed to confirm (y or n) that we want to square-root the entered value.
(a) If y, then the total.value is square-rooted and the resulting square root is written to the total field via the doit( ) function.
(b) If not y, then we are prompt( )ed for a new value to square-root; the a prompt( ) output is square-rooted and (a1/2) is appended to the total.value via the more( ) function.
(2) If the total.value is empty or 0, then we are prompt( )ed for a value to square-root; the a prompt( ) output is square-rooted and a1/2 is written to the total field via the doit( ) function.

If the SC's mode help flag is turned on (1), then we'll get a
This function gives you the square root of any given number alert( ) message
before the if tests and prompt( )ing get under way. The message could be a bit more specific: we all know that we can't take the square root of a negative number...or can we? Actually, we addressed the calculation of imaginary square roots in the It's complex section of Blog Entry #344 during our analysis of the CCC Quadratic Equation script; for the SC the corresponding code could go something like:

var radicand = Number(x); // x = obj.value or a
if (radicand < 0) {
    var real_root = Math.sqrt(-radicand);
    var imag_root = "i \u00d7 " + real_root;
    obj.value = imag_root;
    return; }


Otherwise, Math.sqrt(x) returns NaN if x cannot be converted to a nonnegative number;
per the Non-empty, non-numeric subsection above,
a while (isNaN(x) || x < 0) { x = window.prompt("Your radicand must be a nonnegative number.", ""); }-type statement
can be used to intercept problematic Math.sqrt(x) inputs.

Apropos asides:

Jamie Beyore's Another Great Science Calculator has a key that executes
an external function calc_sqrt(form) { form.expr.value = (Math.sqrt(form.expr.value)) } function whereas
Saries's Rainbow Calculator has an (unsuperscripted) key that executes
an inline document.calculator.text.value = Math.sqrt(document.calculator.text.value) statement.

A standard calculator won't calculate imaginary square roots (at least my Casio won't), but if you go to Google and enter
What is the square root of -9?
in the search field and then click your keyboard's Enter/Return key, you'll get a new page with a
square root(-9) = 3i

div near the top of it.
We'll cover the SC's and functionality in the following entry.

Thursday, June 25, 2020
 
And You, Be Ye Fruitful, and Exponentiate
Blog Entry #410

In the Arithmetic action section of the previous post we used the Super Calculator to add 9 and 7. As we can all add 9 and 7 in our heads without the aid of a calculator, let's up our game by using the SC to raise 9 to the 7th power.

97

We enter a 9 into the total field via the button and the more( ) function as described earlier. We click the button in the SC's Powers section

<td><b>Powers:</b><br>
...
<input type="button" value=" x^y " onclick="getinput(power);">


thereby calling the getinput( ) function and passing thereto a power function reference. I'm not going to put the entire getinput( ) function in front of you (see the whole thing here); the relevant part of it for our present purpose is:

function getinput(func) {
    var a = document.mainform.total;
    if (document.mainform.mode[0].checked) { var mode = 1; } // Help mode
    else { var mode = 0; } // Non-help mode
    if (func == power) { return power(mode, a); } ...


The power argument is given a func identifier.

The total Text object is assigned to an a variable - how's that for a descriptive name? - I'd call it calcText or something like that.

A mode variable is set to 1 if the
Help radio button is checked or to 0 if the
No Help radio button is checked; a conditional (ternary) var mode = document.mainform.mode[0].checked ? 1 : 0; statement can be used here.

The <script>'s power( ) function is called if func and power are equal - true in this case.
Here is the power( ) function from start to finish:

function power(mode, obj) {
    if (mode == 1) { window.alert("Power allows you to give powers to any given number."); }
    if (obj.value != "" && obj.value != "0") {
        aa = window.prompt("Do you want to use the number in the total textbox as the base number?", "y or n");
        if (aa == "y") { a = obj.value; }
        else { a = window.prompt("Enter a base number:", ""); }
        b = window.prompt("Enter an exponent:", "");
        if (aa == "y") { doit(Math.pow(a, b), obj); }
        else { more("(" + Math.pow(a, b) + ")", obj); } }
    else {
        a = window.prompt("Enter a base:", "");
        b = window.prompt("Enter an exponent:", "");
        doit(Math.pow(a, b), obj); } }


The mode number and the a object are passed to the power( ) function and are given the identifiers mode and obj, respectively.

The power( ) function checks if the mode help flag is turned on (it's initially on, as was noted earlier, although you are free to turn it off, of course) and if so displays a
Power allows you to give powers to any given number alert( ) message.
The author/Webmaster sees the [p]ower identifier but the user doesn't, and I would recast the message as:
The x^y key allows you to raise a base number x to the yth power.

The power( ) function tests
if the obj.value is not the empty string AND (&&)
if the obj.value is not the numeric string "0"
and if both conditions are met - our 9 fits the bill, yes? - displays a prompt( ) box that asks us if we want to use the number in the total field for the x base number, which strikes me as an odd sort of thing for a calculator to do, but that's what happens.

Do you want to use the number in the total text box as the base number?
(Now that I think about it, this sort of message belongs on a confirm( ) box.)

As shown above, the default value of the prompt( ) input field (termed the inputDefault back in the day) is set to a y or n string. Some browsers (e.g., Firefox/Netscape, Opera, Safari) select the string: if we're using such a browser, then we can convey our 'yes' by simply pressing the keyboard's y key. The y value is assigned to an aa variable upon clicking the prompt's button.

The power( ) function tests if aa is y - true - and then assigns the obj.value, 9, to an a variable: this a has a global scope because it is not declared with the var keyword (ditto for the above aa variable and the b variable below) and it is wholly distinct from the getinput( ) function's a, which is declared with the var keyword and is therefore local to the getinput( ) function, not that it would make a difference if the former a superseded the latter a.

The power( ) function displays a prompt( ) box that instructs us to enter an exponent, so we type a 7 in the prompt( ) input field. The 7 value is assigned to a b variable upon clicking the prompt's button.

The power( ) function again tests if aa is y - still true - and then
exponentiates a to the bth power via the pow( ) method of the Math object and
sends the ab power, 4782969, and the obj reference for the total field to the doit( ) function,
which we discussed in the Roundabout rounding section of the previous post and
which writes the ab power (renamed obj by the doit( ) declaration) to the total field (renamed where by the doit( ) declaration).

We're not quite done yet. Moving back to the getinput( ) function, note that the power( ) call is preceded by a return keyword, which causes the browser to exit the getinput( ) function after the power( ) function has finished executing. (The aforediscussed doit( ) call is neither preceded nor followed by a return keyword, and the power( ) function returns undefined to the getinput( ) function.)

Two other power( ) possibilities, briefly

If the obj field holds the starting 0 or has been backspaced to a blank,
then the power( ) function will
prompt( ) us for a base (a),
prompt( ) us for an exponent (b), and
raise a to the bth power and load the ab power into the where field via the doit( ) function as described above.

If we do not answer y to the
Do you want to use the number in the total textbox as the base number? prompt
(leaving the y or n inputDefault in place counts as not answering y),
then the power( ) function will
prompt( ) us for a base number (a),
prompt( ) us for an exponent (b), and
raise a to the bth power and wrap the ab power in parentheses and ship the (ab) string and the obj reference to the more( ) function, whose else clause will append the (ab) string to whatever is present in the total field.



Got all that? Quite a bit more involved than the corresponding

function raisePower(x) {
    var y = 0;
    y = window.prompt("What is the exponent?", "");
    document.calculator.text.value = Math.pow(x, y); }


in Saries's Rainbow Calculator script, isn't it?

Squaring and cubing

We can use the button to raise a base number to the 2nd or 3rd power; however, the Powers section has dedicated and buttons

<input type="button" value=" ^2 " onclick="getinput(squared);">
<input type="button" value=" ^3 " onclick="getinput(cubed);">


for these operations. The and buttons are bound respectively to squared( ) and cubed( ) functions that are called from the getinput( ) function à la the power( ) function

if (func == squared) { return squared(mode, a); }
if (func == cubed) { return cubed(mode, a); }


and that are largely analogous to the power( ) function, more specifically, they're a bit simpler than power( ) in that they don't need to get an exponent from the user.

Exponentiation button labels, revisited

A coder will know that the ^ symbols signify exponentiations, but will an ordinary user know that? The Powers heading and the
Help help messages are not an adequate substitute for seeing actual exponents on the Powers buttons, in my opinion.

The button exponent blues, at least for Netscape

The Latin-1 Supplement Unicode block contains a ² superscript two character and a ³ superscript three character, whose HTML numeric character references are &#178; and &#179;, respectively. I find that Navigator 4.05 (recall the SC's best used with Netscape Communicator 4.0 metatext) will not render these characters, e.g., it renders <input type="button" value="x&#179;"> as versus . Meanwhile, the Spacing Modifier Letters Unicode block contains a ʸ superscript y character (&#696;, thanks, Rupert); as you might guess, Navigator 4.05 won't render that one either.

• Level 4 and earlier browsers will display most of the 'upper Latin-1' characters - ©, é, ü, ÷, ... - but they can't handle the ² and ³ or can't quite handle them, e.g., IE 4.5 displays them as normal 2 and 3 characters and not as superscripts.
• The ², ³, and ʸ characters all appeared in Unicode 1.0.0, the very first version of Unicode, which went live in October 1991.

At the beginning of Blog Entry #75 I mooted the use of graphical submit buttons for calculator keys whose labels feature uncodable symbols - would this approach have worked for the Powers buttons way back when? I accordingly create
an x_to_the_yth.gif image
(I clip it from a screen shot, Netscape 4.x's buttons have a #dddddd background color)
and buttonize it via
<input name="power_gsb" type="image" src="x_to_the_yth.gif" onclick="getinput(power);">.
In the event:
With Navigator 4.05, clicking the power_gsb button does not trigger the getinput( )/power( ) code but it does cause the mainform form to submit; the latter action can be choked off by giving the form an onsubmit="return false;" attribute.
With IE 4.5, the power_gsb button is invisible until I give it some sort of styling* - even an otherwise effectless style="display:inline;" will do - but upon doing that and clicking the button, the getinput( )/power( ) code runs without incident. (*I don't know if this is a browser/plug-in bug or an artifact of my SheepShaver setup - this page makes me suspect the former.)

• The <input type="image"> element has no reflection in classical JavaScript; it should map to the Submit object, but it doesn't. Btw, an <input type="submit" value="x^y" onclick="getinput(power); return false;"> button works just fine.

Of course, we would today deploy <button type="button">x<sup>n</sup></button> buttons in the Powers section. Navigator 4.05 supports the sup element but not the button element; IE 4.5 supports both elements.

A buttonless workaround

The following code works with Navigator 4.05 and IE 4.5 and also with Navigator 3.04 and Communicator 4.61 and IE 5.1.7 and modern browsers:

<a href="" onclick="getinput(power); return false;" style="display:inline;"><img width="21" height="18" border="1" src="x_to_the_yth.gif" alt=""></a>

A graphical submit button may be a problematic onclick carrier, but there are no problems with a corresponding link carrier. As a general rule, I don't like to use a link for a non-linking purpose, but a coder's gotta do what a coder's gotta do, eh?

• The link href is set to an empty string, which would normally cause a page reload when we click the link-image, but we can stop that from happening with a return false too.
• Per the preceding subsection, the style="display:inline;" is for IE 4.5's benefit; none of the other browsers needs it.
• Attaching the style="display:inline;" to the <img> element gives rise to some bizarre image-moving behavior with Navigator 4.05 and Communicator 4.61 - I'll spare you the details.
• A style="position:relative;" visualizer, which should also be effectless, causes Navigator 4.05 and Communicator 4.61 to crash.
There are actually three other SC keys that relate to exponentiation - , , and - and we'll discuss them and their underlying functions in our next episode.

Monday, April 27, 2020
 
SC II
Blog Entry #409

OK, let's get back to our discussion of the CCC Super Calculator; we are at present ready to segue from structure to behavior by taking on the SC's JavaScript.

The supercalc.html <script> code comprises no fewer than twenty-five functions. A sprawling getinput( ) function at the top of the script serves as a gateway to seventeen of those functions. Outside getinput( )'s jurisdiction are more( ), calc( ), cleart( ), less( ), helppi( ), doit( ), and helpround( ) functions that are relevant to the Arithmetic and remainder parts of the calculator and that we will cover in today's post.

Arithmetic action

Suppose we want to add 9 and 7. We click the calculator's button

<input type="button" value="  9  " onclick="more(9, document.mainform.total);">

thereby calling a more( ) function.

function more(obj, where) {
    if (where.value == "" || where.value == "0") { where.value = obj; }
    else { where.value += obj; } }


The number 9 and a document.mainform.total object reference for the total I/O text field

<input type="text" name="total" size="20" value="0">

are passed to the more( ) function and are given the identifiers obj and where, respectively. If the where.value is the empty string* or the numeric string "0" - true in this case - then it is set to obj; if a non-0 value were already present in the where field, then obj would be appended to that value.
*The starting "0", or any inputted value, can be backspaced to a blank.

• As for a standard calculator, the total.value is initialized to 0.
• Usually, the user display for a standard calculator is right-justified; Netscape 4.x won't text-align:right; the total field but modern browsers will.
• FYI, the default size for an <input type="text"> is 20. If the SC had a lot of text boxes with a size="20" attribute, then I would delete those attributes, but just one? Leave it.



Clicking the and buttons similarly calls the more( ) function and appends + and 7 characters to the 9 per more( )'s else clause.

To calculate and display the sum, we click the button

<input type="button" value="  =  " onclick="calc(document.mainform.total, document.mainform.total);">

thereby calling a calc( ) function.

function calc(obj, objw) {
    if (objw.value == "") { objw.value = ''; }
    objw.value = eval(obj.value); }


The calc( ) call strangely passes two document.mainform.total references to the calc( ) function: the arguments[0] argument is given an obj identifier and the arguments[1] argument is given an objw identifier. The calc( ) function eval( )s the obj.value and then parks the result in the objw field.



Prior to carrying out the sum, calc( ) tests if the objw.value is an empty string: if true, then the objw.value is pointlessly reset to an empty string.

Is there any need to use two Text objects here? If there is, I don't see it. Moreover, eval('') returns undefined with all of the browsers on my computer: a blank should be converted to a 0 for this case. I would consequently rewrite the calc( ) function as:

function calc(obj) { if (! obj.value.length) obj.value = 0; obj.value = eval(obj.value); }

I should lastly note that we can also get the sum by typing 9+7 in the total field and clicking the button as the total field is not readonly (the readonly attribute was standardized in HTML 4, which postdates the SC).

More arithmetic more( ) buttons and the additive inverse button

Clicking the other digit buttons,
the decimal point button,
the other arithmetic operator buttons,
and the parentheses buttons
loads the corresponding characters into the total field via the more( ) function as described above.

The additive inverse button is bound to a separate negpos( ) function that we will discuss at a later point.

C/d

The button

<input type="button" value=" PI " onclick="helppi( ); more('(' + Math.PI + ')', document.mainform.total);">

also calls on more( ) in order to load π into the total field although the details are a bit different. Clicking the button first calls a helppi( ) function

function helppi( ) {
    if (document.mainform.mode[0].checked) { window.alert("PI is an easy function that just gives you PI. 3.14......"); } }


that pops up a
PI is an easy function that just gives you PI. 3.14...... message
if the
Help radio button is checked, as is true initially.

<b>Mode:
<input name="mode" type="radio" checked>Help
<input name="mode" type="radio">No Help
<!-- No </b> is present in the source. -->


Subsequently, a '(3.141592653589793)' string - yep, 15 fractional digits - is shipped off to more( ). I think the parentheses are there to prevent the π value from being appended to an already-present preceding number.



JavaScript (the browser's JavaScript engine) does not see 5(3.141592653589793) as a multiplication, however; you'll have to input an * symbol between the 5 and the (3.141592653589793) to calculate the circumference of a circle whose diameter is 5. In practice, eval('5(3.141592653589793)') throws a 5 is not a function TypeError.

My Casio fx-85v calculator outputs π to seven places past the decimal point (3.1415927), which is good enough for me, and therefore I would send Math.PI.toFixed(7) to more( ) instead.

Clearing and backspacing

Clicking the SC's button calls a cleart( ) function that clears the total field by resetting it to 0 à la a standard calculator.

function cleart(obj) { obj.value = "0"; }

<input type="button" value="  C  " onclick="cleart(document.mainform.total);">


Clicking the SC's button calls a less( ) function that backspaces the total.value; like a standard calculator's (clear entry) button, the button allows the user to delete the rightmost number of a larger expression without having to start over.

function less(obj) { obj.value = obj.value.substring(0, obj.value.length - 1); }

<input type="button" value=" <-- " onclick="less(document.mainform.total);">


I don't like the button's label or location:
I'd relabel it (no dashed arrows, please)
and put it next to the clearing button as Saries's Rainbow Calculator does.
FWIW, my Casio fx-85v has a backspace button that will backspace an inputted value but not an outputted value, e.g., upon dividing 10 by 7, it won't backspace the 1.4285714 quotient; in contrast, the less( ) function backspaces all total.values.

Roundabout rounding

Twelve of the getinput( )-gated functions call on an external doit( ) function to overwrite the total.value with a new obj value.

function doit(obj, where) { // For all cases, where = document.mainform.total
    where.value = obj; }


The SC's button for rounding a non-integer to an integer also calls on the doit( ) function.

<input type="button" value="Round" onclick="helpround( ); doit(Math.round(document.mainform.total.value), document.mainform.total);">

As shown above, the rounding is carried out by a Math.round(document.mainform.total.value) operation embedded in the doit( ) call. The round( )ed output is passed to doit( ) as the arguments[0] doit( ) argument and then is written to the total field.

Perhaps you are thinking, "The cleart( ) and less( ) functions have just one parameter. Why don't we send document.mainform.total to a dedicated function roundValue(obj) { obj.value = Math.round(obj.value); } function instead?" Well, that would be a more straightforward way to go, wouldn't it?

Prior to the doit( ) call the button calls on a helpround( ) function

function helpround( ) {
    if (document.mainform.mode[0].checked) { window.alert("Round is an easy function that simply rounds the number in the total textbox to the nearest whole number."); } }


that pops up a
Round is an easy function that simply rounds the number in the total textbox to the nearest whole number message
if theHelp radio button is checked. The term "whole number" generally means 0 or a positive integer but not a negative integer (although some people use "whole number" and "integer" interchangeably), so we should probably change whole number to integer in the alert( ) text as the round( ) method duly rounds negative numbers as well as positive ones.
We'll go after the SC's exponentiation and trigonometry operations in the following entry.

Friday, April 03, 2020
 
Super Calc v2.3
Blog Entry #408

The next Calculators, Calendars, and Clocks item is a Super Calculator ("SC" hereafter), which went live at Java Goodies in September 1997 and was created by Tom Richardson, Jr. The SC code is posted here.

We've covered a JavaScript calculator twice previously.
(1) Blog Entries #74 and #75 go through Saries's Rainbow Calculator.
(2) Blog Entries #369, #370, and #371 go through Jamie Beyore's Another Great Science Calculator.
The SC is operations-wise similar to these calculators; it has some features that they don't have - e.g., it provides a memory facility and an extensive alert( )-based help system - and vice versa.

Super structural summary

Set the controls for the heart of the calculator

The SC includes 47 controls in total, they being
a name="total" I/O text field,
41 push buttons for the various input and operation keys plus a push button for an About feature near the bottom of the calculator,
two hidden fields for memory inputs, and
two radio buttons for turning its help system on and off, respectively.
These controls are accessed (and were rendered back in the day) via a containing name="mainform" form.

The mainform form content includes the tables and outro material described below, and is horizontally centered on the page by an unclosed <center> element. Here's a screen shot of the original supercalc.txtsupercalc.html display:

The original, unmodified supercalc.txt display

Lay of the land

The SC display is laid out with six borderless tables.

var tables = document.forms["mainform"].getElementsByTagName("table");

The tables[0] table holds the big Super Calc v2.3 heading and the Best used with Netscape Communicator 4.0 metatext.

The tables[1] table holds the business end of the calculator in its entirety - everything between the Best used with Netscape Communicator 4.0 text and the button - and itself serves as a container for the tables[2]-tables[5] tables.

The tables[2] table holds the Scientific Notation, Memory, and Special functions sections on the left-hand side.

Moving to the middle, the tables[3] table holds the Mode radio buttons and the 0-defaultValued total field and the key whereas the tables[4] table holds the ... keys.

Lastly, the tables[5] table holds the Powers, Finding "x", Fractions, and Misc sections on the right-hand side.

I am normally lenient as regards leaving <table> markup in place for these old scripts, but it would clearly be stretching it to say that we are dealing with grids of data in this case: the tables can and should be exchanged for an equivalent set of divs.

Intro, horizontality, key headings

The Super Calc v2.3 heading is actually coded with a font element:

<font color="red" size="6"><b><center>Super Calc v2.3</b></font>
<!-- Nope, there's no </center> tag here either. -->


As it happens, the size="6" setting and an h1 element both give a 32px font-size, so we can more appropriately code the heading with:

h1 { color: red; text-align: center; }

<h1>Super Calc v2.3</h1>


An h1 heading provides a <p>-like separation between the heading and the Best used with Netscape Communicator 4.0 text; as shown by the above screen shot, the font formulation puts the heading and the text in the same line box (Joe's /JSBook/supercalc.html demo inserts a <br> between them).

The Netscape Communicator 4.0 link points to an http://cgi.netscape.com/cgi-bin/upgrade.cgi resource, which is no longer live (you wouldn't expect it to be) although archived versions of it are still available, not that we want to be encouraging Netscape Communicator 4.0 use in this day and age, of course.

The tables[1] table horizontally separates the tables[2] table, the tables[3]-tables[4] tables, and the tables[5] table via a cellspacing="20" attribute. If we give the tables[1] table → div an id="calcDiv" and contain the tables[3]-tables[4] controls with a single div, then we can approximately reproduce the horizontal separation via a #calcDiv div { display: inline-block; margin: 10px; } styling.

The Scientific Notation, Memory, ... headings are not put in <th> cells but are simply <b>olded. Alternatively, we can legitimately mark up the headings with the label element:

label { display: block; font-weight: bold; margin: 2px; }

<div id="calcDiv">
<div>
<label>Scientific Notation:<br />
<input type="button" value="Scie" onclick="getinput(scie);" />
<input type="button" value="UnScie" onclick="getinput(unscie);" />
</label> ...


Outro

The SC display concludes with some 'fine print' - I'll call it that as it's wrapped in a <font size="-50"> element whose size="-50" setting gives an x-small font-size.

The fine print's first line warns us:
Note: This script is NOT compatible with Microsoft's Internet Explorer 3.0

<font color="red"><blink>Note: This script is NOT compatible with Microsoft's Internet Explorer 3.0</font></blink><br>

We learned a couple of entries ago that IE 3.01 for the Mac won't act on onclick commands, which come up aplenty in the supercalc.html source; I have no idea what problem(s) may arise on the Windows side. The warning is reddened by a font element and is also marked up with a blink element (note that the font and blink elements are improperly nested), which does cause it to blink with the various versions of Netscape on my computer but is not supported by modern browsers. If this were a genuinely important message, then maybe we would blink it by toggling its visibility between visible and hidden, but it clearly isn't.

The fine print's second line asserts:
"[The SC is] the most powerful Web-based calculator in the world."
Perhaps this was true at the time of posting, and it's a boast that holds up surprisingly well today in that most modern-day JavaScript calculators do not venture beyond basic arithmetic - don't take my word for it, run a javascript calculator Google search and see for yourself.

The fine print's third and last line is a by Tom Richardson Jr. authorship credit. The Tom Richardson Jr. link points to an http://home.rmci.net/gooftroop resource, which is not available (if it ever was). The credit is approximately right-justified via 40 preceding spaces (36 &nbsp;s, 4 newlines).

Atop the fine print is an button that when clicked pops up an 'IP notice':
Super Calc v2.3(R) with new more compact Equasion+(R). Both Copyright(C) and authored by Tom Richardson Jr of Richardson Technologies(R)

<input type="button" value="About" onclick="window.alert('Super Calc v2.3(R) with new more compact Equasion+(R). Both Copyright(C) and authored by Tom Richardson Jr of Richardson Technologies(R)');">

• For an alert( ) message or any other JavaScript string, an ® registered trademark symbol can be coded with \u00ae and a © copyright symbol can be coded with \u00a9.

Try it out:

Operational overview

Arithmetic
Like all calculators, the SC can add, subtract, multiply, and divide.
It has a additive inverse key but not a reciprocal key.
It doesn't have a percent key or a modulo key.

Exponentiation, square root, logarithm
The SC has and keys for squaring and cubing a number; moreover and more generally, it has an key for raising a number to the yth power.
It has a key for calculating the square root of a number; it doesn't have a key for calculating the yth root of a number.
It has a key for calculating the natural logarithm of a number; it doesn't have a key for calculating the common logarithm of a number.

Trigonometry
The SC has , , and keys for calculating the sine, cosine, and tangent of an angle in radians.

General
The SC has an key for eval( )uating an expression in the total field.
It has a key for clearing the total field and also a backspace key for deleting the total.value's rightmost character.
It has and parentheses keys that can be used to circumvent the MDAS (×/÷ over +/-) order of arithmetic operations.

Memory
The SC has and memory in (M+) keys and corresponding and memory recall (MR) keys and an memory clear key.

The remainder
The SC has a key for inputting π (3.14159...).
It has a key for rounding a number with a fractional part to the nearest integer.
Finally, it has , , , , and keys for operations that are, shall we say, 'off the beaten track' for a calculator - we'll detail them later.

Operational details

We'll do this next time.

Monday, March 09, 2020
 
JavaScript Is the New Excel
Blog Entry #407

Instant spreadsheet grid

Before leaving the College Tuition Chooser behind, I'd like to outline an alternative, JavaScript-based approach to the creation of a spreadsheet template; I'll confine the following discussion to the structure and presentation of the template and leave it to you to functionally kit out the structure as you see fit.

In the fourth part of this series we showed that the chooser's cost values can be added and cleared iteratively. More fundamentally, the chooser <input>s themselves can be created and deployed iteratively - why write out all that HTML if we don't have to?

We'll frame our template with a simple spreadsheetDiv div versus a table.

<form id="spreadsheetForm" name="spreadsheetForm">
...pre-spreadsheet controls...
<div id="spreadsheetDiv"></div>
</form>
...post-form JavaScript...


The original chooser table layout comprises 11 rows and 4 columns, and can be expanded or shrunk as desired. It would be better if the user could (within reason) set the number of rows and columns from the get-go:

Number of desired input rows: <input name="inputrows" size="5" /><br />
Number of desired input columns: <input name="inputcols" size="5" /><br />


We'll create the template via a createSpreadsheet( ) function that is called by clicking a button.

<button type="button" onclick="createSpreadsheet( );">Create My Spreadsheet</button>

<script type="text/javascript">
var spreadsheetForm = document.getElementById("spreadsheetForm");
var spreadsheetDiv = document.getElementById("spreadsheetDiv");
function createSpreadsheet( ) { ... }
... </script>


The worksheet cells of a standard spreadsheet are indexed with a top-edge row of A, B, C, ... identifiers and a left-edge column of 1, 2, 3, ... identifiers. Let's set the stage for adding these axes to the template by +1-ing the inputrows and inputcols numbers.

var inputrows = Number(spreadsheetForm.elements["inputrows"].value);
var inputcols = Number(spreadsheetForm.elements["inputcols"].value);
var allrows = inputrows + 1;
var allcols = inputcols + 1;


We're ready to create and deploy the template cells and format them a bit:

spreadsheetDiv.style.whiteSpace = "nowrap";
for (var i = 0; i < allrows * allcols; i++) {
    var cellInput = document.createElement("input");
    cellInput.style.width = "80px";
    cellInput.style.border = "solid 1px black";
    cellInput.style.margin = "0px";
    spreadsheetDiv.appendChild(cellInput);
    if (i % allcols == allcols - 1) spreadsheetDiv.appendChild(document.createElement("br")); }


The template rows are terminated not by the right edge of the viewport but by <br> line breaks placed after the i % allcols == allcols - 1 cells, e.g., if allcols is 10, then <br>s are placed after the i = 9, 19, 29, ... cells.

In the name of completeness: Upon setting the cellInput size to 10, bolding the A-Z headers (vide infra) causes the header cell width to shrink slightly with Firefox although not with other browsers, so I changed the size="10" setting to an approximately equivalent width:80px; setting, which is unaffected by the header bolding.

We can respectively populate the top axis's i = 1-26 fields with A-Z headers via:

var colIDs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /* This string can be defined globally, prior to the createSpreadsheet( ) function. */
if (1 <= i && i <= allcols - 1) { /* We'll address the colIDs.length < i situation in the next section. */
    cellInput.value = colIDs.charAt(i - 1); }


Alternatively, the fromCharCode( ) method of the String object can be pressed into service for this operation.

cellInput.value = String.fromCharCode(64 + i);
/* The decimal Unicode/ASCII code positions of the uppercase alphabetic characters run from 65 to 90. */


The headers can be bolded, horizontally centered, and readonly-ed in the normal way:

cellInput.style.fontWeight = "bold";
cellInput.style.textAlign = "center";
cellInput.readOnly = true;


We can respectively populate the left axis's i = allcols, allcols × 2, allcols × 3, ... fields with 1, 2, 3, ... headers via:

if (! (i % allcols)) { cellInput.value = i ? i / allcols : ""; }

The chooser's cost fields were given names matching their coordinates: a1, b1, c1, etc. Let's follow suit:

cellInput.name = (allcols < i) && (i % allcols) ? colIDs.charAt((i - 1) % allcols) + Math.floor(i / allcols) : cellInput.value;

The header fields are named per their values (A, B, C, ... 1, 2, 3, ...) but as a practical matter those fields don't need to be named at all.

Limits

Computer Hope's "How many sheets, rows, and columns can a spreadsheet have?" page reports that the worksheet for recent versions of Excel comprises 1,048,576 rows and 16,384 columns. Do we want to go that big with our template? I don't think so - sounds like a recipe for inducing a hang, if you ask me.

That said, we should be setting the bar higher than a 26-column worksheet.
We can use an

if (colIDs.length < i)
    cellInput.value = colIDs.charAt(Math.floor((i - 1) / colIDs.length - 1)) + colIDs.charAt((i - 1) % colIDs.length);


statement to extend the A-Z headers two-dimensionally à la a standard spreadsheet.

A ... Z AA, AB, ... AZ BA ... BZ ... ZA ... ZZ

This'll give us 702 (26 + 262) columns, which should suffice for most users.

The worksheet for the AppleWorks 5.0 spreadsheet module comprises 500 rows and 40 columns; template creation with these numbers takes about 7 seconds on my computer.

Demo, validation, erasure

I've baked a bit of validation into the demo below.
• Per AppleWorks 5.0, the inputrows number is capped at 500 and the inputcols number is capped at 40 - again, we don't want to cause any unnecessary freezes if we can help it.
• If an inputrows/inputcols value is less than 1 (a blank input is mapped to 0) or is NaN (the input was neither a number nor a blank), then it is set to 1.
• A decimal point-containing inputrows/inputcols number is Math.round( )-ed to the nearest integer.

if (500 < inputrows) { inputrows = 500; spreadsheetForm.inputrows.value = 500; }
if (inputrows < 1 || isNaN(inputrows)) { inputrows = 1; spreadsheetForm.inputrows.value = 1; }
if (/\./.test(inputrows)) { inputrows = Math.round(inputrows); spreadsheetForm.inputrows.value = Math.round(inputrows); } ...


The demo's JavaScript includes a clearAll( ) function that clears the inputrows and inputcols fields and zeroes out the spreadsheet

function clearAll( ) {
    spreadsheetForm.reset( );
    spreadsheetDiv.innerHTML = ""; }
/* In practice, I set the spreadsheetDiv.innerHTML to a set-at-the-outset metatext string - this is a demo, after all... */


and is called by clicking a button.

<button type="button" onclick="clearAll( );">Clear Everything</button>

OK, here we go - check the page source for the full coding.

Number of desired input rows:
Number of desired input columns:


Your spreadsheet template will be generated in this div and below this line of text.

We'll move on to a Super Calculator script in the following entry.

Sunday, February 09, 2020
 
Approaching Excel, Part 5
Blog Entry #406

Today's post will conclude our College Tuition Chooser discussion by tying up some loose ends and presenting a demo.

Last words on browser support and validation

As noted in the third and fourth parts of this series:
Netscape's JavaScript support began with Navigator 2.0. The chooser can be run by Netscape 2.x upon making some simple changes to the computeForm( ) function and by Netscape 3+ as is, the chooser's if (agentInfo == "Netscape 4.0")-gated JavaScript notwithstanding.

Internet Explorer's JavaScript (more precisely, JScript) support began with IE 3.0. I reported earlier that IE 4.5 handles the chooser's for-Netscape 4.0 code without any problems but I didn't say anything about IE 3 as I didn't have it on my computer at the time. I've recently downloaded IE 3.01 from MacFixer's Vintage Mac Software Library and installed it in the SheepShaver environment. As it happens, IE 3.01 - at least on the Mac side - won't run the chooser because it doesn't respond to change and click events at all - this is not altogether surprising given that onEvent element attributes were not part of HTML 3.2, which became a W3C Recommendation a few months before IE 3.01 for the Mac was released - conversely, Quirksmode's Early event handlers page intimates that IE 3 for Windows users should have been good to go. Anyway...

I previously recommended the use of a regular expression to sort out the chooser's character validation issues. Alternatively and far more simply, we can block non-numberizable cost inputs with the isNaN( ) function:

if (isNaN(input.value)) {
    window.alert("Your input can contain digits and a properly placed decimal point, or be blank; anything else doesn't cut it.");
    window.setTimeout(function ( ) { input.focus( ); input.select( ); }, 100); return; }


• Re the above alert( ) message, a blank input is OK - its isNaN( ) return is false - as isNaN( ) maps the empty string to 0.
• A $1,000 input is not OK as its $ and comma characters prevent its conversion to a number; however, you may prefer that the user keep those characters out of the input in the first place.
• Additional code would be needed to flag a number with a fractional part running past the hundredths place or a negative number.

Regardless of what we do or don't do validation-wise, it would be a good idea to preface the chooser with some metatext that specifies what the inputs can and can't be, and I'll do that for the demo.

Average it

In my former life as an instructor of organic chemistry*, I used spreadsheets to calculate exam averages as part of my regular record-keeping activities, and it occurred to me that adding an averaging capability to the chooser would be a useful thing to do.
*Some of my teaching materials from back in the day can still be downloaded as .pdf files here.

We can output averages for the various cost types to the cells of a new, fifth column on the right side of the chooser.

<tr><th>Name of Institution</th> ... <th>Average</th></tr>
<tr><th>Tuition</th> ... <td><input type="text" name="d1" size="15"></td></tr>
<tr><th>Fees</th> ... <td><input type="text" name="d2" size="15"></td></tr>
...
<tr><th>Total Cost of Attendance</th> ... <td><input type="text" name="d9" size="15"></td></tr>


The getAverage( ) function below will add up and average one row of costs à la a standard spreadsheet:

function getAverage(input) {
    var columnID = new Array("a", "b", "c");
    var rowID = input.name.charAt(1);
    var nonblank = 0;
    var runningfieldtotal = 0;
    for (var i = 0; i < columnID.length; i++) {
        if (input.form.elements[columnID[i] + rowID].value) {
            nonblank++;
            runningfieldtotal += Number(input.form.elements[columnID[i] + rowID].value); } }
    var rowaverage = runningfieldtotal / nonblank;
    input.form.elements["d" + rowID].value = rowaverage.toFixed(2); }

function calc(input) {
    if (validateData(input)) {
        getTotal(input);
        getAverage(input); } }


I didn't do any play-by-play for the modified computeForm( ) functions in the previous post, so let me do so in this case.

We set up a columnID Array of the a and b and c column identifiers.

We use the name property of the Text object and the charAt( ) method of the String object to identify (rowID) the row that holds the onchanged input.

We'll count the number of nonblank fields in the row and store that number in a nonblank variable. The division operation that computes the average should exclude blank fields: 10000 and 8000 and a blank should give a 9000 average and not a 6000 average.

We for-iterate across the columnID columns: if a columnID[i] + rowID field is nonblank, then the nonblank count is increased by one and the field's value is Number( )-ized and the resulting number is added to a runningfieldtotal.

When the loop has run its course, the runningfieldtotal is divided by the nonblank count to give the rowaverage.

The rowaverage is toFixed(2) to the hundredths place** whether it reaches that place or not, e.g., a 9000 average becomes 9000.00; the formatted average is displayed in the fifth column's d + rowID field.
**If you would rather the average be a whole number, then you can Math.round( ) the rowaverage instead.

Like the computeForm( )getTotal( ) function, the getAverage( ) function is called from the calc( ) function after the input.value has passed validation.

Vertical tabbing, take one

With some spreadsheet applications, e.g., the AppleWorks spreadsheet module I used way back when, pressing the Enter/Return key effects a vertical tabulation from the current cell to the cell directly below it. I like this feature, so I'm going to add it to the chooser, as follows:

<body>
<form name="costsForm" onsubmit="return false;"> ...chooser form element content... </form>
<script type="text/javascript">
function tabdown(e) {
    var columnID, rowID, thisKey;
    if (e) {
        columnID = e.target.name.charAt(0);
        rowID = e.target.name.charAt(1);
        thisKey = e.key ? e.key : e.which; }
    else if (window.event) {
        columnID = window.event.srcElement.name.charAt(0);
        rowID = window.event.srcElement.name.charAt(1);
        thisKey = window.event.keyCode; }
    else {
        window.alert("Sorry, your browser does not support the event object.");
        return; }
    if (thisKey == "Enter" || thisKey == 13) // If the user presses the Enter/Return key:
        document.forms["costsForm"].elements[columnID + (Number(rowID) + 1)].focus( ); }
for (var i = 0; i < document.forms["costsForm"].elements.length - 2; i++)
    document.forms["costsForm"].elements[i].onkeydown = tabdown;
</script>
</body>


The above code accommodates the Netscape event model and the Internet Explorer event model. Yes, I know that Microsoft support for the Netscape event model dates to IE 9 and that the window.event object now has a legacy status, but so what? Suppose that 0.01% of the world's online population are browsing with IE 5.5-8: that's 100 people per million users, and if I can bring them into the loop with a modicum of effort, then why shouldn't I do so?

A tabdown( ) function that listens for keydown events is iteratively registered on all of the chooser form's controls except the and buttons.

When a keydown event occurs in a chooser field, the tabdown( ) function uses the target or srcElement property of the keydown event object to access the field and uses name.charAt( ) operations to get the field's columnID and rowID coordinates. Subsequently, tabdown( ) uses the key, which, or keyCode property of the keydown event object to determine which key was depressed.

The srcElement, which, and keyCode properties are deprecated but are nonetheless supported by the current versions of all of the major browsers, including Firefox if they are yoked to an e-type event object.

If the depressed key was the Enter/Return key, then e.key returns Enter whereas e.which and window.event.keyCode return 13, and if that's what we've got, then focus( ) is sent to the columnID column's Number(rowID) + 1 field.

The i count includes the Name of Institution fields, which can get in on the vertical tabbing action if their title1, title2, and title3 names are changed to a0, b0, and c0, respectively.

The OpenOffice spreadsheet module's Enter/Return behavior is a bit more complicated.
One or more horizontal tabulations followed by an Enter/Return keypress gives a newline-like transfer of focus (A1 B1 C1 A2).
In the absence of prior horizontal tabbing, OpenOffice behaves like AppleWorks does (A1 A2).
Can we reproduce this behavior JavaScriptically? Maybe, I'd have to give it some thought...

There's more to the spreadsheet tabulation topic that we could discuss - you may know that pressing Shift + Tab effects a leftward tab and that pressing Shift + Enter/Return effects an upward tab - oh, and what about the ➡ and ⬇ and ⬅ and ⬆ arrow keys? - but let's move on.

A new interface?

As detailed previously, a standard spreadsheet requires the user to put some sort of formula in an output cell. Once that formula is in place, the spreadsheet's I/O action parallels that of the chooser, i.e., onchange-ing a formula input value returns automatically a new formula output value.

The chooser puts its addition formulas in the computeForm( ) function; the user doesn't see or handle these formulas at all, which initially rubbed me the wrong way - I felt that this lessened the user's 'conscious agency' in adding up the cost values, shall we say. I gave some thought to getting rid of all those onchange="calc(this);" attributes and instead using and buttons to call functions that would calculate respectively all of the column totals and all of the row averages at once - clicking a push button is the normal JavaScript way of setting something in motion, after all - but ultimately decided that it would actually be kind of annoying to have to click those buttons over and over again.

Perhaps we could make the chooser arithmetic a little less obscure by setting the defaultValues of the Total Cost of Attendance and Average fields to standard formulas - =SUM(A1:A8), =AVERAGE(A1:C1), etc. - or would this just clutter up the display? What do you think?

Demo

The demo of this section is a work in progress, but that's true of all creative efforts, isn't it? The demo incorporates the bulk of the new coding I've put forward in the last several entries - check the page source for all the details from start to finish.

Addition is carried out by the One column at a time computeForm( )getTotal( ) functionality. Averaging is carried out by the getAverage( ) function given above; I've appended a getAverage(input.form.elements[columnID + 9]); call to the getTotal( ) function body so that the Total Cost of Attendance row is averaged along with the input row in question. Validation is carried out by a new validateData( ) function containing the Segue to regular expressions statements. The Back to the beginning resetting facility has been deployed mostly as is - there are two minor edits thereto.

Per the limitations of Blogger, all styles are effected JavaScriptically. Moreover, the Total Cost of Attendance and Average fields have been made readonly, per my preference.

var costsForm = document.forms["costsForm"];
for (var i = 0; i < costsForm.elements.length - 2; i++) {
    costsForm.elements[i].style.textAlign = i < 3 ? "center" : "right";
    if (i % 4 == 2 || i > 34) costsForm.elements[i].readOnly = true; }


Lastly, the streamlined calc( ) coordinator is bound to the cost input fields JavaScriptically so as to tidy up the HTML a bit more.

for (var i = 3; i < costsForm.elements.length - 6; i++) {
    if (i % 4 == 2) continue;
    costsForm.elements[i].onchange = function ( ) { calc(this); } }


OK, here we go. The following spreadsheet can be used to sum and average costs automatically for three institutions of learning.
Your cost inputs may contain
digits running from the hundred-thousands place to the hundredths place,
a starting $,
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;
nonconforming inputs will be intercepted.

Institutional Cost of Attendance Worksheet
Name of Institution Average
Tuition
Fees
Books/Supplies
Board
Room
Personal/Recreation
Transportation
Other
Total Cost of Attendance


Powered by Blogger

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