reptile7's JavaScript blog
Saturday, May 12, 2007
I'm Controlling and Composing
Blog Entry #75

A quick note before getting underway...

In our discussion of the square root key in the previous post, I should have mentioned that an image of a symbol (or of anything else) can also be placed on an input element, more specifically, on an <input type="image"> graphical submit button. For example, we can code the key as

<input type="image" name="sqrt" alt="Square root symbol" src="image_path/square_root_sign.gif"
onclick="return squareRoot( );" />

and the accompanying squareRoot( ) function as:

function squareRoot( ) {
document.calculator.text.value = Math.sqrt(document.calculator.text.value);
return false; }

The return false statement in the squareRoot( ) function will cancel sqrt's submitting action and effectively convert sqrt to a generic push button. (The W3C notes here that the submit event is cancelable.) This is obviously not the most elegant of solutions but is nonetheless a workable alternative for older browsers (e.g., Netscape 4.x) that do not recognize the button element.

Quick note #2:

In the source of the Script Tips #52-55 Script demo page, the script's author, "Saries", leaves us a message:
You can use this script if you do me 3 favours:
1) Visit
2) Don't take credit for this script.
3) Leave this message here.
Fair enough, mate, but the link is dead; send me an updated contact link and we'll give you a shout-out.

OK, fellow math fans - that means you too, Emily - let's get back to the Script Tips #52-55 Script and the 'rainbow calculator' that it codes. We pick up the conversation by turning at last to the = key, which Joe covers at the end of Script Tip #54:

<input type="button" value=" = " name="equals" onclick="document.calculator.text.value = eval(document.calculator.text.value);">

Unlike the other output keys, the = key is meant to act not on a number but on an arithmetic expression, which is evaluated via the top-level eval( ) function. For example, keying in 5+7-4-2 and clicking the = key puts 6 in the text field.

The eval( ) function takes a string argument, and we have noted several times previously (e.g., in Blog Entry #35) that text box values are de facto string objects.

In evaluating arithmetic expressions, eval( ) follows the conventional ("PEMDAS") order of mathematical operations: 2+3*4 gives 14, whereas (2+3)*4 gives 20.

Missing keys

We can easily extend the functionality of the Script Tips #52-55 calculator; here are some keys that I would add:

<button type="button" name="root"> x<sup>1/y</sup> </button>
<!-- You may want to apply a font-weight:bold style rule to the superscript if it's rendered as faintly on your computer as it is on my iMac. -->

Although the extraction of roots can be accomplished indirectly via the x^y key, most users will prefer a more straightforward approach thereto. We can follow the calculator form with a script element that (a) contains or (b) references a file that contains:

document.calculator.root.onclick = extractRoot;
function extractRoot( ) {
var y = window.prompt("Please enter a root y:");
document.calculator.text.value = Math.pow(document.calculator.text.value,1/y); }

(2-3) and
<button type="button" name="ten_to_the_x"> 10<sup>x</sup> </button>
<button type="button" name="e_to_the_x"> e<sup>x</sup> </button>

No calculator is complete without the ability to calculate powers of ten. We can follow the calculator form with a script element that contains or references:

document.calculator.ten_to_the_x.onclick = powerof10;
function powerof10( ) {
document.calculator.text.value = Math.pow(10,document.calculator.text.value); }

We can exponentiate e (2.71828...) via the exp( ) method of the Math object:

document.calculator.e_to_the_x.onclick = powerofe;
function powerofe( ) {
document.calculator.text.value = Math.exp(document.calculator.text.value); }

(4-5) and
<input type="button" value=" log " name="log" />
<input type="button" value=" ln " name="ln" />

When I first saw the Script Tips #52-55 calculator, my immediate reaction was, "Where are the logarithm keys??" In rectification of this egregious omission, we turn to the log( ) method of the Math object; dividing the Math.log(x) return by 2.303 gives a common logarithm, whereas not dividing by 2.303 gives a natural logarithm. In the post-calculator script element, put:

document.calculator.log.onclick = commonLog;
function commonLog( ) {
document.calculator.text.value = Math.log(document.calculator.text.value)/2.303; }

document.calculator.ln.onclick = naturalLog;
function naturalLog( ) {
document.calculator.text.value = Math.log(document.calculator.text.value); }

(6-7) and
<input type="button" value=" Min " name="Min" />
<input type="button" value=" MR " name="MR" />

We can make use of the script's seemingly useless <input name="list" type="hidden"> element (the W3C briefly discusses hidden controls here) to store temporarily and then retrieve numbers/expressions via "memory in" and "memory recall" keys, respectively. In the post-calculator script element, put:

document.calculator.Min.onclick = memoryIn;
function memoryIn( ) {
document.calculator.list.value = document.calculator.text.value; }

document.calculator.MR.onclick = memoryRecall;
function memoryRecall( ) {
document.calculator.text.value += document.calculator.list.value; }

(If you're going to add memory keys to the calculator, then the Clear key should be left as

<input type="button" value=" Clear " name="clear" onclick="document.calculator.text.value = '';" />

and not changed to

<input type="reset" value=" Clear " />

per my recommendation in the previous post.)

<input type="button" value=" x! " name="fact" />

Finally, we can use a for loop to add a factorial capability to the calculator; in the post-calculator script element, put:

document.calculator.fact.onclick = factorial;
function factorial( ) {
var x = 1;
for (i=document.calculator.text.value; i>0; i--) {
x *= i; }
document.calculator.text.value = x; }

Review references in the JavaScript 1.5 Core Reference:
• The for statement is described here.
• The -- decrement operator is discussed here.
• Shorthand assignment operators are listed and defined here.

It is left to the reader to code additional keys, e.g., %, sin-1, cos-1, tan-1, M+, etc.

Data Validation

Like any interactive script, the Script Tips #52-55 Script works fine if it is 'used properly'; however, as we've noted previously, part of a programmer's job is to anticipate and, if possible, preempt invalid user input. In a sense, the Script Tips #52-55 calculator comes with its own data validation: it will act on dodgy numbers and expressions, but the results won't be particularly meaningful - "garbage in, garbage out", so to speak. For example, if you key in and click the key, then the calculator displays NaN in the text box; if you key in )5+6(*7 and click the = key, then you'll get a syntax error. Of course, most calculators won't let you input or )5+6(*7 in the first place...

Towards a normal calculator

There are other ways in which the operation of the Script Tips #52-55 calculator differs from that of a 'normal' calculator, e.g.:
• Calculators typically display 0 when they are turned on; the Script Tips #52-55 calculator doesn't display anything (the text box's value is set to an empty string).
• Unlike the Script Tips #52-55 calculator, most calculators don't display the +, -, ×, and ÷ arithmetic operators.
• Moreover, we can safely assume that there are no hand-held calculators whose xy keys pop up prompt( ) boxes asking for exponents. ;-)

You may or may not be bothered by, or might even prefer, the alternative operation of the Script Tips #52-55 calculator. In any case, it is worthwhile to ask, "Can we make the Script Tips #52-55 calculator behave like a normal calculator?" Some of my efforts towards this end are summarized below.

An initial display of 0

<!-- This one's easy: -->
<input type="text" name="text" value="0" />

Not displaying +, -, *, and /

Here's the basic strategy we'll use:
(a) as for the original calculator, we'll build an arithmetic expression, but in a hidden field; in the process thereof,
(b) we'll display the operands (but not the operators), and also the final result, in the visible text field.
For part (a) above, we can again make use of the list control, and while we're at it, let's drag the calculator form's start-tag outside of the table element, where it belongs:

<form name="calculator">
<input name="list" type="hidden" value="0" />
<table border="1" cellspacing="2" cellpadding="3%">
<!-- You did notice that the form and table elements are improperly nested, didn't you? -->

Let's follow the calculator form with a script element that begins:

<script type="text/javascript">
var myForm = document.calculator;
var operator = /[+*\/-]$/;

(Regarding the operator regular expression: MSIE throws "Error: Invalid range in character set" if the short dash (for subtraction) is not the first or last character of the character set; Netscape throws "Error: unterminated character class" if the forward slash (for division) is not escaped with a backslash.)

Here's my preferred code for the + key (the other arithmetic operator keys can be recoded analogously):

<!-- In the HTML: -->
<input type="button" value=" + " name="add" />
// In the post-calculator script element:
myForm.add.onclick = sum;
function sum( ) {
myForm.list.value += "+"; }

Here's my code for the 7 key (the other numeric keys can be recoded analogously):

<!-- In the HTML: -->
<input type="button" value=" 7 " name="but7" />
// In the post-calculator script element:
myForm.but7.onclick = input7;
function input7( ) {
if (operator.test(myForm.list.value) || myForm.text.value == 0) {
myForm.text.value = 7; myForm.list.value += 7; }
else {
myForm.text.value += 7; myForm.list.value += 7; } }

Briefly, if the preceding character of the list value is an arithmetic operator or if the text value is 0, then 7 replaces the text value and is concatenated to the list value; otherwise, 7 is concatenated to both the text and list values.

We'll code the = key in the "exponentiation" subsection below.

Only one decimal point per number, please

Now that we're displaying only one operand at a time in the text field, we can easily restrict that operand to only one decimal point by coding the . key as:

<!-- In the HTML: -->
<input type="button" value=" . " name="decimal" />
// In the post-calculator script element:
myForm.decimal.onclick = period;
function period( ) {
if (operator.test(myForm.list.value)) {
myForm.text.value = "0."; myForm.list.value += "0."; }
if (/^(\+|-)?\d+$/.test(myForm.text.value)) {
myForm.text.value += "."; myForm.list.value += "."; } }

Briefly, for a . to be placed in the text field, the text value must conform to the /^(\+|-)?\d+$/ regexp pattern, which only matches positive or negative numbers without decimal points.

A prompt( )-less exponentiation

This is a bit tricky, but is doable. When I exponentiate on my Casio fx-85v, I key in the base number x, and then press the xy key, and then key in the exponent number y, and then get the resulting power by pressing the = key. Here's how we can mimic this behavior on the Script Tips #52-55 calculator:

(1) First, let's code the xy (originally x^y) key as

<button type="button" name="power"> x<sup>y</sup> </button>

and have it trigger the following raisePower( ) function:

myForm.power.onclick = raisePower;
function raisePower( ) {
myForm.list.value = "Math.pow(" + myForm.list.value + ","; }

(2) Next, let's add a comma to the operator character set:

var operator = /[,+*\/-]$/;

(3) Finally, let's code the = key as

<input type="button" value=" = " name="equals" />

and have it trigger the following result( ) function:

myForm.equals.onclick = result;
function result( ) {
if (/^M/.test(myForm.list.value))
myForm.list.value += ")";
myForm.text.value = eval(myForm.list.value); }

Briefly, inputting a base x and clicking the xy key builds and assigns to the value of list a Math.pow(x, string, to which y and ) characters are added by inputting an exponent y and clicking the = key, respectively; the = key also evaluates the completed Math.pow(x,y) string and displays the return in the text field.

It's a start. Try it out below:

There's more 'normalizing' work we could do here, but I think our time is better spent moving on to the next script, namely, the guitar chord script of Script Tips #56-59, which we'll take up in the following post.


Comments: Post a Comment

<< Home

Powered by Blogger

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