reptile7's JavaScript blog
Wednesday, May 30, 2007
 
Here Come the Warm Radio Buttons
Blog Entry #77

We return now to the guitar chord chart script of HTML Goodies' JavaScript Script Tips #56-59 and its first table cell (td) element. The first table cell consists of a script element that can be subdivided into four parts:
(1) A large array maps guitar chords onto their corresponding finger positions.
(2) A pair of nested for loops generates the first cell's initial display: a 'guitar' with a mini-headstock, a nut, and a six-fret fretboard.
(3) A showChord( ) function writes a chord's finger positions to the fretboard.
(4) A parser( ) function disassembles the values of the chord-position array to give number arrays that serve as inputs for the showChord( ) function.

Joe begins his script deconstruction in Script Tip #56 with the two for loops, as will we. Joe offers a reasonable description of what happens in general terms, but he glosses over several matters, so we will fill in the blanks.

Here's the code that we're interested in:

for (Countx = 1; Countx < 8; Countx++) {
var Count, Countx;

for (Count = 1; Count < 7; Count++) {
document.write("<input type='radio' onclick=''>"); }

document.write("<br>");

if (Countx == 1)
document.write("<img src='bb.gif' width='150' height='3' align='absmiddle'>");
else
document.write("<img src='bb.gif' width='150' height='1' align='absmiddle'>");

document.writeln("<br>"); }

We've encountered for loops a number of times previously; go here if you need a review.

Counter variables

The outer and inner for loops run according to the counter variables Countx and Count, respectively. Strangely, Countx is declared, along with Count, after the outer for declaration. In fact, the var Count, Countx; expression can be commented out without any problems. However, if you do want to use the var keyword to declare these variables - "[I]t is good style to use var," Netscape states in the var statement entry in the JavaScript 1.5 Core Reference - then you should do so either before the outer for declaration or in the for declarations themselves:

var Countx, Count;
for (Countx = 1; Countx < 8; Countx++) { // etc.
// or
for (var Countx = 1; Countx < 8; Countx++) { // etc.

As far as I am aware, this is the first time we've used a single expression to declare ≥2 variables, and I wondered, "Is this legit?" The var statement syntax given by Netscape shows that, yes, it's legit:

var varname [= value] [..., varname [= value] ]

The inner for loop

The inner for loop writes to the page a row of six radio buttons; radio buttons are discussed by the W3C here. Towards the end of forming chords on the fretboard, these radio buttons have deliberately not been given a name attribute so that more than one radio button can be checked simultaneously. In practice on my iMac, I can make multiple selections from a set of radio buttons lacking a name attribute when using MSIE 5.1.6, but Netscape 7.02 will only allow me to check one radio button, as though a name attribute were present; however, when I give each radio button a different name via the code below, I find that I can make multiple selections from the resulting set of buttons when using either MSIE or Netscape:

document.write("<input type='radio' name='r" + Countx + Count + "' onclick=''>");
// The radio buttons will be named r11, r12, r13, etc.

Relatedly, these radio buttons have been given an onclick='' attribute. Joe explains:
Note the radio button has an onClick Event Handler set to empty single quotes. That's important. That makes it so that when the button is clicked, nothing happens. You don't want these buttons clicked. They will fill in on their own.
Perhaps when the button is clicked, nothing happens on Joe's computer, but on my computer, I can 'mousically' check onclick='' radio buttons to my heart's content; however, I find that an

onclick='this.checked=false;'

attribute effectively cancels the click event in this case.
(FYI: onclick='return false;' shuts down mousical radio button checking when using MSIE but, unexpectedly, not when using Netscape.)

The outer for loop

To give the first table cell's 6×7 grid of radio buttons, the outer for loop writes to the page the inner for loop's row of radio buttons seven times; the rows are separated by line breaks and also by lines formed from a 2px-by-2px image, bb.gif, which is horizontally "stretched" to 150 pixels via a width='150' attribute.
(BTW, HTML Goodies' "So, You Want A 1 X 1 Image, Huh?" tutorial provides a collection of colored 2px-by-2px images that are useful for creating colored lines and blocks of color on a Web page.)

For the outer for loop's first iteration (Countx=1), the bb.gif line is supplied with a height='3' attribute to give the nut of the first cell fretboard. For the outer for loop's other six iterations, the bb.gif line is supplied with a height='1' attribute to give the frets of the first cell fretboard.

All seven bb.gif lines have been given an align='absmiddle' attribute. The align attribute of the img element was deprecated in HTML 4.0 and has W3C-approved values of top, middle, bottom, left, and right. The nonstandard absmiddle value is supported by both Netscape and MSIE, and aligns the middle of an image with the middle of the text in the current line, to use Netscape's definition. However, the bb.gif nut and frets don't share their lines with text or anything else, and thus the align='absmiddle' attributes serve no purpose and can/should be removed.

The writeln( ) method of the document object

Each bb.gif line is followed by a line break (br element) written to the page with a document.writeln( ) command. Similar to a document.write(x) command, a document.writeln(x) command writes to the page its argument x but appends thereto a newline (\n), e.g.:

document.writeln("Here is some text.");
// is equivalent to
document.write("Here is some text.\n");

In almost all cases, a newline in HTML is equivalent to a space, and thus in almost all cases document.writeln(x) does not force a line break after x; consequently, document.writeln(x) and document.write(x) are generally interchangeable. A couple of exceptions:

(1) Inside of a pre element, document.writeln(x) forces a line break.

<pre>
<script type="text/javascript">
document.writeln("Do Re Mi Fa");
document.write("So La Ti Do");
</script>
</pre>

Output:
Do Re Mi Fa
So La Ti Do

(2) In writing textarea element content, document.writeln(x) can force a line break.

<script type="text/javascript">
document.write("<textarea cols='40' rows='3'>");
document.writeln("Do Re Mi Fa");
document.write("So La Ti Do");
document.write("</textarea>");
</script>

Output:


(FWIW: I find that document.writeln(x) also forces a line break in the long-obsolete listing element.)

Getting back to the outer for loop, the pre-if-statement document.write("<br>") can be written as document.writeln("<br>"), and the post-else-statement document.writeln("<br>") can be written as document.write("<br>"), with no observable difference in the first table cell's display. Interestingly, however, we can push the guitar's 'strings' - the vertical columns of radio buttons - a bit further apart, giving IMO a nicer display, if we change the inner for loop's document.write( ) command to a document.writeln( ) command, thus putting spaces between the radio buttons in each row - we'll illustrate this in just a bit.

You can see from my demo in the previous post that the document.write("<br>") line breaks lead to a rather scrunched fretboard, and my guess is that the script's author was trying with the document.writeln("<br>") command to enlarge the interfret distance, bringing us to...

<hr /> frets

You may be wondering, "Why are we using an image to make the frets? Why don't we use the hr element?" Indeed, why don't we?

for (Countx = 1; Countx < 8; Countx++) {
for (Count = 1; Count < 7; Count++) {
document.writeln("<input type='radio' name='r" + Countx + Count + "' onclick='this.checked=false;' />"); }
document.write("<br />")
if (Countx == 1) document.write("<hr class='nut' />");
else document.write("<hr class='fret' />"); }

Here's the CSS we'll use:

td { text-align: center; }
td#cell0 { background-color: blanchedalmond; }
td#cell1 { background-color: black; }
hr {
color: black;
background-color: black;
width: 150px;
border: 0px; }
hr.nut { height: 3px; }
hr.fret { height: 1px; }
/* The hr rule above gives a solidly black nut and frets when using either MSIE or Netscape. */

And here's the display:



Beginning to look like a real fretboard, eh? The block-level rendering of the hr element increases the vertical space between the nut/frets and the rows of radio buttons. If desired, you can use the CSS margin-top and margin-bottom properties to push the nut/frets/radio buttons even further apart.

Expanding or contracting the fretboard

Let's get back to Countx and Count for a moment. Suppose you would like to restock the Script Tips #56-59 Script with a catalogue of barre chords, for which you clearly don't want to be limited to six frets. (Actually, the script's chord menu does include three fully barred chords: Fm, F#, and F#m.) Generating an n-fret fretboard is a simple matter of resetting the Countx upper boundary in the outer for declaration to n+2; for example,

for (Countx = 1; Countx < 22; Countx++) { // etc.

will produce a standard 20-fret fretboard. Let's suppose that you are also interested in adapting the Script Tips #56-59 Script to the creation of chord charts for the 5-string banjo and the ukulele; setting the Count upper boundary in the inner for declaration to 6 or 5 will produce a fretboard with 5 or 4 strings, respectively:

for (Count = 1; Count < 5; Count++) { // etc.; for a ukulele or perhaps a 4-string banjo

More challengingly, can you code a 'quartet of doublets' 8-string display for a mandolin?

We'll check over the first table cell's guitar-chord-finger-position array in the next post.

reptile7

Sunday, May 20, 2007
 
I've Got Blisters on my Fingers!
Blog Entry #76

Today's post begins a look at one of the most interesting scripts at the HTML Goodies site: the guitar chord chart script that spans Script Tips #56, #57, #58, and #59. In brief, the Script Tips #56-59 Script presents a menu of guitar chords and, when the user selects a chord, the corresponding finger positions are displayed on a simulated fretboard to the left of the menu.

The Script Tips #56-59 Script can be located here in HTML Goodies' /legacy/beyond/javascript/stips/ subdirectory (the "Here's the Code" links in Script Tips #56-59 all lead to scriptless pages) and is reproduced in the div below:

<table border="2">

<td bgcolor="ffffff" cellpadding="0" cellspacing="0" align="center">

<form name="guitar">

<script language="javascript">

<!-- 
var chords = new Object( );
chords["A"] = "1;5;14;15;16";
chords["A7"] = "1;3;5;14;16";
chords["A9"] = "3;4;5;14;25;30";
chords["A13"] = "1;3;20;22;23";
chords["Am"] = "1;5;10;14;15";
chords["Am6"] = "0;1;10;14;15;17";
chords["Am7"] = "30;32;33;34";
chords["Bb"] = "7;20;21;22";
chords["B"] = "13;26;27;28";
chords["B7"] = "4;8;13;15;17";
chords["B9"] = "8;13;15;16";
chords["Bm"] = "13;22;26;27";
chords["Bm6"] = "2;4;9;13;17";
chords["Bm7"] = "2;4;13;15;17";
chords["C"] = "3;5;10;14;19";
chords["C6"] = "5;10;14;15;19";
chords["C7"] = "5;10;14;19;21";
chords["Cmaj7"] = "3;4;5;14;19";
chords["C9"] = "5;14;19;21;22";
chords["Csus4"] = "3;10;19;20";
chords["C7sus4"] = "4;19;20;21";
chords["Cdim7"] = "8;10;12;15";
chords["Cm"] = "19;28;32;33";
chords["Cm7"] = "19;21;28;32";
chords["D"] = "2;15;17;22";
chords["D6"] = "1;2;4;15;17";
chords["D7"] = "2;10;15;17";
chords["Dmaj7"] = "1;2;15;16;17";
chords["D9"] = "26;31;33;34;35";
chords["Dsus4"] = "1;2;15;22;23";
chords["D7sus4"] = "1;2;10;15;23";
chords["Ddim7"] = "2;4;9;11";
chords["Dm"] = "2;11;15;22";
chords["Dm7"] = "1;2;10;11;15";
chords["Eb9"] = "20;25;27;28;29";
chords["E"] = "0;4;5;9;13;14";
chords["E7"] = "0;2;4;5;9;13";
chords["E9"] = "0;2;9;13;17;22";
chords["E13"] = "0;2;5;9;13;16";
chords["Em"] = "0;3;4;5;13;14";
chords["Em6"] = "0;3;5;13;14;16";
chords["Em7"] = "0;3;5;13;14;22";
chords["F"] = "10;11;15;20";
chords["F5"] = "6;19";
chords["F6"] = "1;2;6;10;15";
chords["Fmaj7"] = "1;5;6;10;14;15";
chords["Fm"] = "6;9;10;11;19;20";
chords["Fm7"] = "6;8;9;10";
chords["F#"] = "12;16;17;21;25;26";
chords["F#7"] = "5;12;14;16;21";
chords["F#9"] = "7;9;12;14;16";
chords["F#m"] = "12;15;16;17;25;26";
chords["F#m7"] = "5;15;16;26";
chords["G"] = "2;3;4;13;18;23";
chords["G5"] = "24;37";
chords["G6"] = "14;18;22;27";
chords["G7"] = "2;3;4;11;13;18";
chords["Gmaj7"] = "18;22;26;27";
chords["G9"] = "4;15;18;20";
chords["G6/9"] = "14;15;22;23";
chords["Gsus4"] = "2;3;10;18;23";
chords["G7sus4"] = "3;10;18;20;23";
chords["Gdim7"] = "3;7;14;16;18";
chords["Gm"] = "2;3;7;18;22";
chords["Gm7"] = "18;20;21;22";

for (Countx = 1; Countx < 8; Countx++)
{
var Count, Countx;
for (Count = 1; Count < 7; Count++) 
{
document.write ("<input type='radio' onClick=''>");
} 

document.write ("<br>");
if (Countx == 1)
document.write ("<img src='bb.gif' width='150' height='3' align='absmiddle'>");
else
document.write ("<img src='bb.gif' width='150' height='1' align='absmiddle'>");
document.writeln("<br>");
}

function showChord( ) 
{
var Item, Ret, Count, Skip;
for (Count = 0; Count < 42; Count++) 
{
document.guitar[Count].checked = false;
}
Item = document.guitar.chord.selectedIndex;
if (Item != -1) 
{
Text = document.guitar.chord.options[Item].text;
Frets = parser(chords[Text]);
for (Count = 1; Count <= Frets[0]; Count++) 
{

document.guitar[parseInt(Frets[Count])].checked = true;
}
}
}

function parser(InString) 
{
var Sep = ";", NumSeps = 1, Count, Start, ParseMark, parse; 
for (Count = 1; Count < InString.length; Count++) 
{
if (InString.charAt(Count) == Sep)
NumSeps++;
}
parse = new Array( );
var Start = 0, Count = 1, ParseMark = 0, LoopCtrl = 1;
while (LoopCtrl == 1) 
{
ParseMark = InString.indexOf(Sep,ParseMark);
TestMark = ParseMark;
if ((TestMark == 0) || (TestMark == -1))
{
parse[Count] = InString.substring(Start,InString.length);
LoopCtrl = 0;
break;
}
parse[Count] = InString.substring(Start,ParseMark);
Start = ParseMark + 1, ParseMark = Start, Count++;
}
parse[0] = Count;
return (parse);
}

// -->

</script>

</td>
<td align="center" bgcolor="000000">

<select name="chord" size="7" onchange="showChord( );">
<option>A
<option>A7
<option>A9
<option>A13
<option>Am
<option>Am6
<option>Am7
<option>Bb
<option>B
<option>B7
<option>B9
<option>Bm
<option>Bm6
<option>Bm7
<option>C
<option>C6
<option>C7
<option>Cmaj7
<option>C9
<option>Csus4
<option>C7sus4
<option>Cdim7
<option>Cm
<option>Cm7
<option>D
<option>D6
<option>D7
<option>Dmaj7
<option>D9
<option>Dsus4
<option>D7sus4
<option>Ddim7
<option>Dm
<option>Dm7
<option>Eb9
<option>E
<option>E7
<option>E9
<option>E13
<option>Em
<option>Em6
<option>Em7
<option>F
<option>F5
<option>F6
<option>Fmaj7
<option>Fm
<option>Fm7
<option>F#
<option>F#7
<option>F#9
<option>F#m
<option>F#m7
<option>G
<option>G5
<option>G6
<option>G7
<option>Gmaj7
<option>G9
<option>G6/9
<option>Gsus4
<option>G7sus4
<option>Gdim7
<option>Gm
<option>Gm7
</select>

</form> 
</td>
</tr>

</table>

Joe's attempted script demo in Script Tips #56-59 does not work for two reasons:
(1) More importantly, the script's [ and ] characters have been unnecessarily and fatally escaped to &#91; and &#93;, respectively.
(2) Less importantly, the path to the bb.gif image that composes the fretboard's frets is incorrect; the src URL should be /images/bb.gif (and not simply bb.gif).
That leaves it to us to craft our own demo:



The script's chord menu comprises no fewer than sixty-five chords, and should thus prove a helpful learning tool for both beginning and experienced guitarists.
(A13? D7sus4? As I recall, it was knowing these sorts of chords that got Glen Matlock kicked out of the Sex Pistols. But I digress.)

Before we put the Script Tips #56-59 Script under the microscope, I should mention that Joe himself is a guitarist, and the Hobbies tab on his homepage at the Web site of Southeastern Louisiana University, where he teaches, sports
(a) links to photos of his guitars and
(b) a collection of mp3s of his performances thereupon for your listening pleasure.

Table structure

The Script Tips #56-59 Script's display is housed in a one-row, two-cell table. Before we address the contents of the individual table cells, a few comments on table structure are in order.

Sharp-eyed observers will note that the tr element's end-tag is present but its start-tag is missing; however, the HTML 4.01 Specification states that the tr element's start-tag is required and its end-tag is optional. (According to the HTML 4.01 Index of Elements, there are no HTML elements whose start-tags are optional and whose end-tags are required.) Of course, if you would like the script to be XHTML-compliant, then the <tr> and </tr> tags should both be present.

The first table cell element has been given cellspacing="0" and cellpadding="0" attributes; however, cellspacing and cellpadding are attributes of the table element, and not of the td element. Moreover, both cellspacing and cellpadding have default specifications of #IMPLIED, which in practice means, "The browser has its own preferred way of dealing with these attributes, so you don't have to supply them if you don't want to." (#IMPLIED is an SGML keyword that is murkily defined by the W3C here; a better #IMPLIED description appears in the "Attributes" section in Chapter 2 of the Text Encoding Initiative's "A Gentle Introduction to SGML".) When I removed the cellspacing="0" and cellpadding="0" attributes from the first cell's start-tag and put them in the <table border="2"> tag, and then reran the script, I observed no difference vis-à-vis the script's display.

Both table cell elements are equipped with an align attribute and a bgcolor attribute; these attributes can be replaced by the following style block:

td { text-align: center; }
td#cell0 { background-color: white; } /* for a <td id="cell0"> cell */
td#cell1 { background-color: black; } /* for a <td id="cell1"> cell */

FYI: the HTML 4.01 Index of Attributes shows that the align attribute is not deprecated for table element descendants but is deprecated for all other elements that can take an align attribute, whereas the bgcolor attribute is deprecated for all elements that can take a bgcolor attribute.

(While we're on the topic of style: fretboards are typically not white, and you might prefer a bisque (#ffe4c4) or blanchedalmond (#ffebcd) background color for the first cell.)

One more point before moving on: the script's td elements and guitar form element are improperly nested:

<td id="cell0">
<form name="guitar">
<!-- First cell content -->
</td>
<td id="cell1">
<!-- Second cell content -->
</form>
</td>

To rectify this violation of well-formedness, we can and should make the guitar form the parent of the table element:

<form name="guitar">
<table border="2">
<!-- Table body -->
</table>
</form>

Alternatively and less preferably, we could end the guitar form in the first table cell and start a second form in the second table cell (this would require slight modification of two statements in the script's script element):

<td id="cell0">
<form name="guitar">
<!-- First form content -->
</form>
</td>
<td id="cell1">
<form name="guitar2">
<!-- Second form content -->
</form>
</td>

The second cell

The first table cell is far more complex than the second table cell, so let's start with the second cell. The second cell comprises a selection list of sixty-five options, one for each guitar chord. The select element has a size="7" attribute and thus displays seven consecutive options at a time; it doesn't have a multiple attribute, however, and consequently the user can only choose one option at a time.

The option elements all lack </option> tags, which are not required by HTML but, per the discussion above, must be added for XHTML compliance.

We'll take up the first table cell in the next entry.

reptile7

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 www.geocities.com/Colosseum/Bleachers/1258
2) Don't take credit for this script.
3) Leave this message here.
Fair enough, mate, but the www.geocities.com/Colosseum/Bleachers/1258 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:

(1)
<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.)

(8)
<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.3 Client-Side 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 1.2.3.4 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 1.2.3.4 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.

reptile7


Powered by Blogger

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