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("<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:

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

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