reptile7's JavaScript blog
Monday, April 30, 2007
 
I'm the Operator with my Pocket Calculator
Blog Entry #74

We've been working with HTML tables in the last couple of scripts, and we'll see another table application today as we tuck into HTML Goodies' JavaScript Script Tips #52, #53, #54, #55, and the script thereof that uses a table to construct a basic calculator (accessible via the "See It In Action" links in all four script tips):



Admittedly, the Script Tips #52-55 calculator is not as "basic" as is my iMac's Calculator desktop accessory:

The iMac Calculator (This is an image, and not an actual calculator.)

However, it's much simpler than my Casio fx-85v.

As for the Script Tips #52-55 Script itself, you can find it here in HTML Goodies' /legacy/beyond/javascript/stips/ subdirectory (the "Here's the Code" links in Script Tips #52-55 all lead to scriptless pages), or you can take it from the div below:

<html>
<head>

<script language="javascript">

<!-- start hiding

function getPi( ) 
{return Math.PI;}

function getRandom( ) 
{return Math.random( );}

function change( )
{
var temp = document.calculator.text.value;

if (temp.substring(0,1) == "-") 
{document.calculator.list.value = "";
document.calculator.text.value = 0 - document.calculator.text.value * 1;}

if (temp.substring(0,1) != "-") 
{document.calculator.list.value ="";
document.calculator.text.value = "-" + temp.substring(0,temp.length);}
}

function recip(x)
{document.calculator.text.value = (1/(x));}

function raisePower(x)
{
var y = 0;

y = prompt("What is the exponent?", "");

document.calculator.text.value = Math.pow(x,y);
}

// end hiding -->
</script>

<!-- Visible Table Starts Here -->

</head>

<body>

<table border="1" cellspacing="2" cellpadding="3%">
<tr>
<form name="calculator">
<td colspan="5" bgcolor="red">
<center>
<input name="list" type="hidden">
<input type="text" name="text" value="">
</td>
<td colspan="2" bgcolor="red">
<center>
<input type="button" value=" Backspace " name="backspace" onclick="document.calculator.text.value = document.calculator.text.value.substring(0,document.calculator.text.value.length*1 -1);">
</td>
<td colspan="2" bgcolor="red">
<center>
<input type="button" value=" Clear " name="clear"
onclick="document.calculator.text.value = '';">
</td>
</tr>
<tr>
<td bgcolor="blue">
<center>
<input type="button" value=" 7 " name="but7" onclick="document.calculator.text.value += '7';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 8 " name="but8" onclick="document.calculator.text.value += '8';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 9 " name="but9" onclick="document.calculator.text.value += '9';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" + " name="add" onclick="document.calculator.text.value += '+';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" - " name="subtract" onclick="document.calculator.text.value += '-';">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" 1/x " name="reciprocal" onclick="recip(document.calculator.text.value);">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" x^y " name="power" onclick="raisePower(document.calculator.text.value);">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" sin " name="sin" onclick="document.calculator.text.value = Math.sin(document.calculator.text.value*3.141592653589793/180);">
</td>
</tr>
<tr>
<td bgcolor="blue">
<center>
<input type="button" value=" 4 " name="but4" onclick="document.calculator.text.value += '4';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 5 " name="but5" onclick="document.calculator.text.value += '5';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 6 " name="but6" onclick="document.calculator.text.value += '6';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" * " name="multiply" onclick="document.calculator.text.value += '*';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" / " name="divide" onclick="document.calculator.text.value += '/';">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" | x | " name="absolute" onclick="document.calculator.text.value = Math.abs(document.calculator.text.value);">
</td>
<td bgcolor="lime"> 
<center>
<input type="button" value=" exp. " name="exp" onclick="document.calculator.text.value += 'E';">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" cos " name="cos" onclick="document.calculator.text.value = Math.cos(document.calculator.text.value*3.141592653589793/180);">
</td>
</tr>
<tr>
<td bgcolor="blue">
<center>
<input type="button" value=" 1 " name="but1" onclick="document.calculator.text.value += '1';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 2 " name="but2" onclick="document.calculator.text.value += '2';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" 3 " name="but3" onclick="document.calculator.text.value += '3';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" ( " name="leftbracket" onclick="document.calculator.text.value += '(';">
</td>
<td bgcolor="yellow">
<center>
<input type="button" value=" ) " name="rightbracket" onclick="document.calculator.text.value += ')';">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" x&#178; " name="square" onclick="document.calculator.text.value = document.calculator.text.value * document.calculator.text.value;">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" round " name="round" onclick="document.calculator.text.value = Math.round(document.calculator.text.value);">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" tan " name="tan" onclick="document.calculator.text.value = Math.tan(document.calculator.text.value*3.141592653589793/180);">
</td>
</tr>
<tr>
<td bgcolor="blue">
<center>
<input type="button" value=" 0 " name="but0" onclick="document.calculator.text.value += '0';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" . " name="decimal" onclick="document.calculator.text.value += '.';">
</td>
<td bgcolor="blue">
<center>
<input type="button" value=" +|- " name="sign" onclick="change( );">
</td>
<td colspan="2" bgcolor="black">
<center>
<input type="button" value=" = " name="equals" onclick="document.calculator.text.value = eval(document.calculator.text.value);">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" x&#189; " name="sqrt" onclick="document.calculator.text.value = Math.sqrt(document.calculator.text.value);">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" rand " name="random" onclick="document.calculator.text.value = getRandom( );">
</td>
<td bgcolor="lime">
<center>
<input type="button" value=" pi " name="pi" onclick="document.calculator.text.value += getPi( );">
</td>
</tr>
</table>

</form>

</body>
</html>

First, a few words about that table...

Joe notes in Script Tip #52 that the calculator is housed in a five-row, eight-column table. Actually, a close look at the script suggests that the table should have nine columns, because the colspan attribute values for the td elements holding the input field, the Backspace button, and the Clear button in the first table row are 5, 2, and 2, respectively. However, the Clear button is clearly not meant to span two columns, and the colspan="2" attribute of the Clear button's parent td element can be removed. I should mention, while we're on this topic, that HTML Goodies' "So You Want Some Advanced Table Commands, Huh?" tutorial is largely devoted to the colspan and rowspan attributes, which allow td/th table cells to span more than one column and row, respectively.

Also, let's look at the table element's start-tag for a moment:

<table border="1" cellspacing="2" cellpadding="3%">

A cellpadding value of 3%? This seemed fishy to me initially, but it turns out that both cellspacing and cellpadding are %Length; attributes, and can thus take either a pixel-integer or percentage value, so everything's OK here.

Calculator structure

The calculator comprises a five-column text box

<td colspan="5" bgcolor="red"><input type="text" name="text" value=""></td>

and 33 buttons/keys: 20 of these keys input a number or mathematical operator into the text box, whereas the other 13 keys act on numbers or expressions entered by the input keys.

The input keys

Eighteen of the 20 input keys place a single character in the text box:

(1) The 7, 8, 9, 4, 5, 6, 1, 2, 3, 0, and . keys input the corresponding numbers or a decimal point.

(2) The +, -, *, and / keys input the corresponding arithmetic operators, which we've been using since HTML Goodies' JavaScript Primers #14. If you'd rather put respectively the more conventional × and ÷ symbols on the * and / keys, then that's pretty easy to do:

<!--In place of value=" * ", use:-->
value=" &times; " or value=" &#215; "
<!--In place of value=" / ", use:-->
value=" &divide; " or value=" &#247; "
<!--× and ÷ are not ASCII characters but are part of the Latin-1 Supplement ("upper Latin-1") character set.-->

The - key can also be used to input the - of a negative number.

(3) The ( and ) keys input a left and right parenthesis, respectively. As in mathematics, parentheses can be used in JavaScript to override the precedence of other operators.

(4) The exp. key inputs an E for expressing a number using E notation, e.g., 4E2 is equivalent to 4×102 (i.e., 400); the JavaScript 1.3 Client-Side Guide addresses the use of E notation here.

The other two input keys place multidigit numbers in the text box:

(5) The rand key uses the random( ) method of the core JavaScript Math object, which Joe introduced in the Script Tips #16-18 Script, to input a random number between 0 and 1.

(6) The pi key uses the PI property of the Math object to input pi (3.14159...). If you'd rather put the more conventional π symbol on the pi key, then that's pretty easy to do:

<!--In place of value=" pi ", use:-->
value=" &pi; " or value=" &#960; "

The Script Tips #52-55 Script's script element contains getRandom( ) and getPi( ) functions that respectively generate the rand and pi key inputs. Joe notes in Script Tip #54 that both of these functions are unnecessary; for example, the rand key could be coded as:

<input type="button" value=" rand " name="random" onclick="document.calculator.text.value = Math.random( );">

At the same time, there's something to be said for separating the JavaScript code from the HTML. If we were to follow the calculator form with a script element that (a) contains or (b) references a file that contains

document.calculator.random.onclick = getRandom;
function getRandom( ) {
document.calculator.text.value = Math.random( ); }

then we can code the rand key more cleanly:

<input type="button" value=" rand " name="random" />

On my iMac, the rand and pi key inputs have sixteen significant figures. (In contrast, the RAN# and π functions on my Casio fx-85v give numbers with three and eight significant figures, respectively.) If your browser is up-to-date, then you can pare these numbers down to a more reasonable size with the toPrecision( ) method of the core JavaScript Number object, which we discussed at the end of Blog Entry #30. For example, if you would rather the pi key input 3.14159 and not 3.141592653589793, then the retooled getPi( ) function below will do the job:

function getPi( ) {
var pi6 = (Math.PI).toPrecision(6);
return pi6; }

All of the input keys except the rand key use the += shorthand assignment operator, which Joe introduced in the Script Tips #35-37 Script, to add/concatenate their respective characters to the right-hand end of the text box's value, e.g.:

<input type="button" value=" exp. " name="exp" onclick="document.calculator.text.value += 'E';">

The output keys

(1) The Backspace key uses the substring( ) method of the core JavaScript String object, which we fleshed out in the "SBScroll( ) function" section of Blog Entry #65, to chop off the last character of the text box's value:

<input type="button" value=" Backspace " name="backspace" onclick="document.calculator.text.value = document.calculator.text.value.substring(0,document.calculator.text.value.length*1 -1);">

So, if the user keys in 123456 and clicks the Backspace key, then the 6 is removed and 12345 remains in the text box. Regarding the substring( ) command's second parameter, is it necessary to multiply document.calculator.text.value.length by 1? Emphatically not. Moreover, we can use the this special operator to shorten the onclick expression a bit:

onclick="this.form.text.value = this.form.text.value.substring(0,this.form.text.value.length-1);"

(2) The Clear button

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

is merely a reset button, and its coding is more complicated than it needs to be;

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

is all you need here.

(3) The 1/x key converts a number to its multiplicative inverse (e.g., it would convert 100 to 0.01) via a script element function, recip( ). When clicked, the 1/x key passes to recip( ) document.calculator.text.value, which is assigned to a variable x and then divided into 1:

function recip(x) {
document.calculator.text.value = (1/(x)); }
/*Both sets of parentheses on the right-hand side of the statement above can be removed.*/

Function parameterization is unnecessary, however; we can follow the calculator form with a script element that contains or references

document.calculator.reciprocal.onclick = recip;
function recip( ) {
document.calculator.text.value = 1/document.calculator.text.value; }

and then code the 1/x key as:

<input type="button" value=" 1/x " name="reciprocal" />

In the "Reciprocal" section of Script Tip #53, Joe professes unfamiliarity with the 1/x function, which is a standard feature for all but the most basic calculators. More seriously, in his discussion Joe wrongly links the 1/x key to both the recip( ) and raisePower( ) functions in the script element; in fact, the raisePower( ) function has nothing to do with the 1/x key but pertains to the next key, bringing us to...

(4) The x^y key can be used to exponentiate a number x to the yth power. On a typical calculator, the exponentiation key has a xy label, which cannot be put on an <input type="button"> element but can be put on a <button> element:

<button type="button" name="power" onclick="raisePower(document.calculator.text.value);"> x<sup>y</sup> </button>

(According to its content model

<!ELEMENT BUTTON - - (%flow;)* -(A|%formctrl;|FORM|FIELDSET) -- push button -->

a button element can contain just about any document body element except an anchor element, a form element, a form control element, or a fieldset element - in theory, you could put a table on one of these buttons - the input element, in contrast, has an "EMPTY" content model.)

So, the user keys a number into the text box and clicks the x^y key, triggering the script element's raisePower( ) function and passing thereto document.calculator.text.value, which is again assigned to a variable x. Up pops a prompt( ) box that solicits the user for an exponent; the user's prompt( ) input is outputted/assigned to a variable y. Subsequently, x and y are plugged into a Math.pow( ) command:

function raisePower(x) {
var y = 0; // This declaration can be removed.
y = prompt("What is the exponent?", "");
document.calculator.text.value = Math.pow(x,y); }

(Contra the "Power" section of Script Tip #53, raisePower( ) does not exponentiate x "to the first power" (unless the user enters 1 into the prompt( ) box).)

The caret (^) is an exponentiation operator in some programming languages but in JavaScript is a bitwise XOR operator, whose description is outside the scope of this entry.

(5-7) For the trigonometric sin, cos, and tan keys, the script's author anticipated, not unreasonably, that the user would input an angle in degrees. The sin( ), cos( ), and tan( ) methods of the Math object are designed to work with angles in radians (you can input an angle in degrees but, unless you're inputting 0, you won't get a correct return), which are obtainable from angles in degrees via the following unit conversion:

Degrees-to-radians conversion

The sin key is thus coded as:

<input type="button" value=" sin " name="sin" onclick="document.calculator.text.value = Math.sin(document.calculator.text.value*3.141592653589793/180);">

Regarding the Math.sin( ) parameter, we can of course replace 3.141592653589793 with Math.PI:

onclick="this.form.text.value = Math.sin(this.form.text.value*Math.PI/180);"

The cos and tan keys can be recoded analogously.

(In response to Joe's garbled definition of sine - The ratio of the side opposite the angle of the hypotenuse - I recommend this page at TheMathPage.com if you could use a basic trig refresher.)

(8) The | x | key converts an inputted number to its absolute value via the abs( ) method of the Math object. As a means of converting a negative number to its opposite, the | x | key is redundant, as the +|- key, discussed below, does the same thing.

(9) The key squares an inputted number:

<input type="button" value=" x&#178; " name="square" onclick="document.calculator.text.value = document.calculator.text.value * document.calculator.text.value;">

As shown above, it's not necessary to use the button and sup elements to put a superscripted ² on a button; &#178; (or &sup2;) will do the trick. (The &#178; numeric character reference does not appear in the script on the scripttip52script.html page - I had to fish it out of the source.)

If preferred, a Math.pow( ) command could also be used here:

onclick="this.form.text.value = Math.pow(this.form.text.value,2);"

(10) The round key rounds a number in the text box to the nearest integer via the round( ) method of the Math object, which Joe introduced in the Script Tips #16-18 Script:

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

For example, clicking the pi key and then the round key puts 3 in the text box.

(11) The +|- key (labeled +/- on my Casio fx-85v) converts an inputted number to its additive inverse. Joe devotes an entire script tip (#55) to this key, which is associated with the script element's change( ) function and a totally superfluous <input type="hidden"> element. We're not going to discuss the change( ) function, we're just not, because JavaScript has a - unary negation arithmetic operator that allows us to convert positive numbers to their negative counterparts, and vice versa, via a single statement:

<input type="button" value=" +/- " name="sign"
onclick="this.form.text.value = -this.form.text.value;">

That's it - chuck the change( ) function and use the code above.


(12) The key takes the square root of an inputted number via the sqrt( ) method of the Math object:

<input type="button" value=" x&#189; " name="sqrt" onclick="document.calculator.text.value = Math.sqrt(document.calculator.text.value);">

The &#189; numeric character reference does not appear in the script on the scripttip52script.html page, and is again taken from its source. In case you're wondering, 'x&#189;' is the ASCII code to display x1⁄2. Cool, huh? Joe remarks in the "Square Root" section of Script Tip #54. Actually, the &#189; ½ is not an ASCII character but - like ×, ÷, and ² - lies in the upper half of the Latin-1 character set. Interestingly, on my computer the &#189; ½ is not an indivisible character but is rendered as three discrete 1, /, and 2 characters. (So if you were also wondering, "Can't we just type one-slash-two here?", the answer is yes, and IMO, a one-slash-two 1/2 looks a bit nicer than an &#189; ½.)

The ½ should of course be superscripted, which is doable on a button element:

<button type="button" name="sqrt" onclick="this.form.text.value = Math.sqrt(this.form.text.value);"> x<sup>½</sup> </button>

Square roots can alternatively be extracted via a Math.pow( ) command:

onclick="this.form.text.value = Math.pow(this.form.text.value,0.5);"

(As an exercise, you should be able to write a single raisePower( ) function that generates returns for the x^y, , and keys.)

On my Casio fx-85v, the square root key is labeled with a Square root symbol symbol, and we can put a gif image of this symbol on a button element:

<button type="button" name="sqrt" onclick="this.form.text.value = Math.sqrt(this.form.text.value);"><img width="23" height="22" src="image_path/square_root_sign.gif" alt="Square root symbol" /></button>

Output:

Relatedly, an &#8730; or &radic; character reference codes a √ radical symbol, which lacks a vinculum and hence doesn't look as nice.

We'll cover the = key and other aspects of the calculator's operation in the next post.

reptile7

Labels: ,


Wednesday, April 18, 2007
 
Framed Potpourri for 200, Alex
Blog Entry #73

Today's post continues our analysis of the Script Tips #49-51 Script. We begin with a couple of things in the script's HTML that caught my eye...

The tbody element

For the table element, the script specifies a child tbody element, which in turn is the immediate parent of the tr elements. Interestingly, inspection of the content models of the table and tbody elements shows that
(a) a table element necessarily contains one or more tbody elements, whether specified or not, and
(b) the tr element does not appear in the table element's content model but is the only element in the tbody element's content model:

<!ELEMENT TABLE - - (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)>
<!ELEMENT TBODY O O (TR)+ -- table body -->
<!--As for a regular expression, + is a quantifier that means "occurs 1 or more time(s)."-->

The tbody element's start-tag and end-tag are both optional, as you would guess given the large number of HTML tables out on the Web that do not specify tbody elements.

In HTML Goodies' Tables Tutorial sector, the introductory "So, You Want A Table, Huh?" tutorial makes no mention of the tbody element, but the "HTML 4.0: Tables" tutorial contains its own "<TBODY> Tag" section (appropriately so, as the W3C did introduce the tbody element in HTML 4.0). The "HTML 4.0: Tables" tutorial incorrectly portrays the tbody element as a sort of 'tabilized span element' that can surround and format any group of consecutive table cells - note above that the td element is not part of the tbody element's content model; also provided is a tbody element demo that is invalid in two other ways:

(1) The demo's tr and tbody elements are improperly nested.

(2) The tbody element is equipped with a bgcolor="pink" attribute; however, the HTML 4.01 Specification's Index of Attributes shows that the table, tr, td, and th elements can all take a bgcolor attribute, but the tbody element cannot! On the other hand, bgcolor is a listed attribute on the Microsoft Developer Network's "TBODY Element" page.

In any case, Joe's tbody element demo works on my iMac when using either MSIE or Netscape, evidently because its tbody element does not interrupt the two-row, four-cell structure of the 'ancestor' table element - if we try to color only the first three cells of the table

<table border="1">
<tr>
<tbody bgcolor="pink">
<td>Cell Data</td>
<td>Cell Data</td>
</tr><tr>
<td>Cell Data</td>
</tbody>
<td>Cell Data</td>
</tr></table>

then the fourth cell is pushed onto a third table row:

Cell DataCell Data
Cell Data
Cell Data

The reset( ) method

Let's take a closer look at the "Clear All" reset button:

<input onclick="reset( );" type="button" value="Clear All">

In Script Tip #50, Joe says:
The "Clear All" button fires another function named reset( ). Don't bother looking for it [in the script element], it isn't there. "Reset" is a function built into the browser. When you make guestbook forms, you create a reset button through the code:

<input type="reset">

In this case, the coding is just a little different to allow you to put text on the reset button.
I did a double take when I read that last sentence; putting text on a reset button is no more complicated than adding a value="Some text" attribute to the <input type="reset"> code. Moreover, reset( ) is not a top-level function but is a method of the form object; the onclick="reset( );" attribute should be formulated as

onclick="this.form.reset( );"
or
onclick="document.Framer.reset( );"

even if the browser's JavaScript engine is able to execute an objectless reset( ) command.

And now, back to those \r carriage returns...

We briefly commented in the previous post on the \r's that are placed in the Script Tips #49-51 Script's script element; these carriage returns are present solely to improve the readability of the frameset code in the Fillit/Pastebox boxes, and the script will still work if you take them all out. There are at least three other ways to force line breaks in textarea boxes; \r can be replaced with
(a) &#13; (the numeric character reference for a carriage return),
(b) \n (a line feed), or
(c) &#10; (the numeric character reference for a line feed),
but you can't use <br />, as we explained in Blog Entry #60's "textarea element" section.

A couple of view( ) function issues

Regarding the script element's view( ) function, which displays the value of Pastebox in a new window and which is discussed in Script Tip #51:

(1) The if block's return false statement and the else block's return true statement are unnecessary and can/should be removed; unlike for, say, a link or a checkbox, there is no other, default behavior associated with the "View It!" button that we need to override here.

(2) The newly opened boat window and the opener window do not have a parent-child relationship; the parent reference in the variabilization of Pastebox's value thus serves no purpose and can/should be removed, i.e.:

see = document.Framer.Pastebox.value;

Separating structure and presentation

We wrap up this entry with some comments on style.

Let's start with the script's body element, whose five attributes - alink, bgcolor, link, text, and vlink - are all deprecated. Leaving aside the fact that the document body doesn't contain any links, the corresponding style block for the body and anchor elements would be:

body { background-color: #c0c0c0; color: black; }
a:active { color: red; }
a:link { color: #0000ee; }
a:visited { color: #551a8b; }

The :active, :link, and :visited anchor pseudo-classes are discussed here in the CSS 1 Specification.

We next consider the table and td elements. As noted in the "Other code possibilities: two into one" section of Blog Entry #71, the border attribute of the table element is not deprecated, but it is simple enough to write a corresponding CSS table border rule:

table, td { border: 1px outset; }

CSS selector grouping is discussed here. The table selector without the td selector gives an 'external' border but no borders between cells, or at least that's what I see on my computer.

The td element's width attribute, which is deprecated, is set to 150 (pixels) for the northwest and southwest table cells. The td element's valign attribute, which is not deprecated, is (unnecessarily) set to top for the northeast and southeast table cells. The corresponding CSS rules for these attributes can be written as:

td.firstinrow { width: 150px; } /* for <td class="firstinrow"> cells */
td.secondinrow { vertical-align: top; } /* for <td class="secondinrow"> cells */

We lastly turn to the frameset and frame elements. With respect to the bordercolor attribute, I find that a

frameset { border: solid blue; }

rule does not 'bluify' interframe borders when using either MSIE or Netscape (with MSIE, blue borders appear at the browser window's top and left edges, but with Netscape there's no color at all). As for marginwidth and marginheight, these attributes cannot be replaced with a

frame { margin: 0px; }

rule for the frameset document, but a

body { margin: 0px; }

rule in or, preferably, externally applied to the w.htm, x.htm, y.htm, and z.htm frame documents themselves will work.

Lagniappe: a frameset background color?

In Script Tip #49, Joe twice incorrectly states that the variable bc represents a "background color", and this got me to thinking, "Can a frameset element even have a background color?" I went to the script demo page and clicked the "2 Vertical" button. In the Fillit box, I deleted the second (src=x.htm) frame element and then added a style='background-color:green;' attribute to the frameset element start-tag. Upon clicking the "Copy/Edit" and "View It!" buttons, the right half of the pop-up window
(a) is indeed green when using MSIE, but
(b) is white when using Netscape.

We'll give Casio and Texas Instruments a run for their money in the next entry when we code an elementary calculator via the Script Tips #52-55 Script.

reptile7

Tuesday, April 10, 2007
 
Framed
Blog Entry #72

You may recall that we briefly encountered frames for the first time in HTML Goodies' JavaScript Script Tip #41. We delve much more deeply into frames in today's entry as we examine a "Frame Maker" script addressed by Script Tips #49, #50, and #51. The Script Tips #49-51 Script enables a user to create and view various frames layouts, and initially contains six preset frameset templates; prior to its display, however, the frameset code is usefully loaded into textarea boxes, in which it can be edited by the user - a feature that breaks new ground for us interactivitywise.

The Script Tips #49-51 Script can actually be accessed by following the "Here's the Code" link in Script Tip #51 (but not via the corresponding links in Script Tips #49 and #50), and is reproduced in the div below:

<html>
<head>
<title>Frame Maker</title>

<script language="javascript">

// Below are variables that will be used in each paste function.

var top = "<html>" + "\r" + "<title>My Frame Page</title>" + "\r" +"<head></head>";
var nf = "<noframes>" + "\r" + "You need a frames capable browser to view this page." + "\r" + "</noframes>" + "\r" + "</html>";
var f = "</frameset>";
var bc = "bordercolor=blue>";
var MW = "marginwidth=0";
var MH = "marginheight=0";

// Below are the six functions that produce the frame code in the textarea box.

function framesa( ) 
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset cols=50%,* "
+ " " + bc + "\r" + "<frame src=w.htm" +" " + "name=One" 
+ "\r" + "scrolling=auto" + " " + MW + " " + MH + " " + "noresize=yes>" 
+ "\r" + "<frame src=x.htm" +" " + "name=Two"
+ "\r" + "scrolling=auto" + " " + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + f 
+ "\r" + nf;
}

function framesb( ) 
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset rows=50%,* "
+ " " + bc + "\r" + "<frame src=w.htm" +" " + "name=One" 
+ "\r" + "scrolling=auto" + " " + MW + " " + MH + " " + "noresize=yes>" 
+ "\r" + "<frame src=x.htm" +" " + "name=Two"
+ "\r" + "scrolling=auto" + " " + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + f 
+ "\r" + nf;
}

function framemixa( ) 
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset cols=30%,* " + bc + " " + "noresize=yes>"
+ "\r" + "<frame src=w.htm" + " " + "name=One" + " " + "scrolling=yes"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + "<frameset rows=50%,*>"
+ "\r" + "<frame src=x.htm" + " " + "name=Two" + " " + MW
+ "\r" + MH + " " + "scrolling=yes>"
+ "\r" + "<frame src=y.htm" + " " + "name=Three" + " " + "scrolling=no"
+ "\r" + MW + " " + MH + " " + "noresize=no>" 
+ "\r" + f
+ "\r" + f
+ "\r" + nf;
}

function frames3v( )
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset cols=33%,33%,*" + " " + bc
+ "\r" + "<frame src=w.htm name=One" + " " + "scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + "<frame src=x.htm" + " " + "name=Two" + " " + "scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + "<frame src=y.htm" + " " + "name=Three" + " " +
"scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + f
+ "\r" + nf;
}

function frames3h( )
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset rows=33%,33%,*" + " " + bc
+ "\r" + "<frame src=w.htm name=One" + " " + "scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + "<frame src=x.htm" + " " + "name=Two" + " " + "scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + "<frame src=y.htm" + " " + "name=Three" + " " +
"scrolling=auto"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + f
+ "\r" + nf;
}

function framemixb( ) 
{
document.Framer.Fillit.value = top
+ "\r" + "<frameset cols=30%,* " + bc + " " + "noresize=yes>"
+ "\r" + "<frameset rows=50%,*>" + "\r" + "<frame src=w.htm" + " " +
"name=One"
+ " " + "scrolling=no" + " " + MW + "\r" + MH + " " + "noresize=yes>" 
+ "\r" + "<frame src=x.htm" + " " + "name=Two" + " " + MW
+ "\r" + MH + " " + "scrolling=yes>"
+ "\r" + f
+ "\r" + "<frameset rows=50%,*>" + "\r" + "<frame src=y.htm" + " " +
"name=Three"
+ "\r" + "scrolling=no" +" " + MW + " " + MH + " " + "noresize=no>"
+ "\r" + "<frame src=z.htm" +" " + "name=Four" + " " + "scrolling=yes"
+ "\r" + MW + " " + MH + " " + "noresize=yes>"
+ "\r" + f
+ "\r" + f
+ "\r" + nf;
}

// Below is the function that copies over the text from one box to the other.

function Copy( )
{
if (document.Framer.Fillit.value == "") 
{ alert("The top box is empty. Please enter a script by clicking one of the frames buttons."); }
else 
{ document.Framer.Pastebox.value = document.Framer.Fillit.value; }
}

// Below is the function that displays the code in a new window.

function view( )
{
if (document.Framer.Pastebox.value == "") 
{ alert("The paste box is empty. Please enter a script by clicking the Copy/Edit button.");
return false;
}
else 
{ alert("If you like the results, remember to paste it to a text editor!");
boat = open("","DisplayWindow");
see = parent.document.Framer.Pastebox.value;
boat.document.write(see);
return true;
}
}

</script>

</head>

<body alink="#ff0000" bgcolor="#c0c0c0" link="#0000ee" text="#000000" vlink="#551a8b">

// Below is the form and table code that creates the look on the page.

<form name="Framer">
<table border="1">
<tbody>
<tr>
<td width="150">

<b>1.</b> Choose One:<p>

<input onclick="framesa( );" type="button" value="2 Vertical"><br> 
<input onclick="frames3v( );" type="button" value="3 Vertical"><br> 
<input onclick="framesb( );" type="button" value="2 Horizontal"><br> 
<input onclick="frames3h( );" type="button" value="3 Horizontal"><br> 
<input onclick="framemixa( );" type="button" value="3 Mixed"><br> 
<input onclick="framemixb( );" type="button" value="4 Mixed"><br> </td>
<td valign="top">
<textarea cols="56" name="Fillit" rows="15"></textarea> 
<center>
</td></tr>
<tr>
<td>

<b>2.</b> Paste it in:<p>

<input onclick="Copy( );" type="button" value="Copy/Edit"><p> 
<input onclick="alert('Misfire? No problem, a new copy will be pasted to the bottom box. Good Luck!'); Copy( );" type="button" value="Start Over"><br> 
<input onclick="reset( );" type="button" value="Clear All"><br> </td>
<td valign="top">
<textarea cols="56" name="Pastebox" rows="15"></textarea> 

<center>
<b>3.</b> Then: <input onclick="view( );" type="button" value=" View It! ">
</center>

</td>
</tr>
</tbody>
</table>

<p>
</form>
</p>

</body>

</html>

Overview of the Script Tips #49-51 Script

The "See it in Action" links in Script Tips #50 and #51 lead to a script demo page (the corresponding link in Script Tip #49 points to the Script Tips #45-48 Script demo page). The Script Tips #49-51 Script demo page is in the form of a large two-row, four-cell table. The northwest cell holds a "1. Choose One:" heading and a series of six push buttons; when clicked, each button calls a function in the script's script element.

(1) The "2 Vertical" button calls the framesa( ) function, which contains a single assignment statement. Using the global variables at the beginning of the script element - top, nf, f, etc. - the right side of the statement pieces together and stringifies the code for a two-column frames layout:
The framesa( ) layout
The frameset document string is formatted/shaped with several "\r" carriage returns and then displayed in the Fillit textarea box in the table's northeast cell.

(2) The "3 Vertical" button calls the frames3v( ) function, which similarly assembles and loads into the Fillit box the code for a three-column frames layout:
The frames3v( ) layout

(3) The "2 Horizontal" button calls the framesb( ) function, which assembles and loads into Fillit the code for a two-row frames layout:
The framesb( ) layout

(4) The "3 Horizontal" button calls the frames3h( ) function, which assembles and loads into Fillit the code for a three-row frames layout:
The frames3h( ) layout

(5) The "3 Mixed" button calls the framemixa( ) function, which assembles and loads into Fillit the code for a column-row-row three-frame layout:
The framemixa( ) layout

(6) The "4 Mixed" button calls the framemixb( ) function, which assembles and loads into Fillit the code for a four-frame layout:
The framemixb( ) layout

Moving on to the table's second row, the southwest cell holds a "2. Copy it in:" heading and a series of three push buttons. Both the "Copy/Edit" and "Start Over" buttons use the script element's Copy( ) function to copy and paste Fillit's contents into the Pastebox textarea box in the table's southeast cell. The "Clear All" button, as you might guess, functions as a reset button.

Finally, at the bottom of the southeast cell is a "View It!" button that, via the script element's view( ) function, displays Pastebox's code in a new browser window.

The Copy( ) and view( ) functions both include some don't-leave-it-blank-type data validation, popping up an alert( ) message if Fillit or Pastebox is empty, respectively.

Frameset/frame element attributes

Joe doesn't discuss the attributes of the frameset and frame elements to any extent, so perhaps we should do that.

Frameset attributes

(1) The cols and rows attributes of the frameset element are fairly self-explanatory, but what about those asterisks, huh?

<frameset rows="50%,*">

You can probably figure out that the * in the tag above is equivalent to 50%. More precisely, the * value is a relative length representing the remaining available space, as explained in the "Lengths" section of the HTML 4.01 Specification; cols="30%,*" and cols="30%,70%" are thus also equivalent.

(2) The bordercolor attribute, appearing in the global variable bc, adds color to the borders between frames; I say "adds color to" because, at least on my iMac, the bordercolor="blue" border is not solidly blue but contains blue and black regions:

A bordercolor border, up close

The bordercolor attribute was first implemented by Netscape in Navigator 3.0, and support by MSIE followed shortly thereafter; however, bordercolor is not 'standardized': you won't find it in the HTML 4.01 Specification's Index of Attributes.

Buried in the HTML Goodies site (and shamefully no longer appearing in the Frames Tutorial sector) is a "So You Want Colored Frames, Huh?" legacy tutorial that discusses the bordercolor attribute.

Frame attributes

(1) A frame's src attribute, much like an img src attribute, specifies a file that will load into the frame. (The frame and img elements are both 'placeholder' elements having an "EMPTY" content model.)

(2) A name attribute allows a frame to be targeted by a link in another frame - see the "Naming Cells and Using Targets" section of HTML Goodies' "So, You Want Some Frames, Huh?" tutorial for more information and a demo.

(3) The scrolling attribute has three possible values:
(a) scrolling="auto" installs vertical and horizontal scrollbars if a frame needs them (i.e., if the src file's display overflows the frame's visible area), and doesn't install scrollbars if a frame doesn't need them;
(b) scrolling="no" ensures that a frame won't have scrollbars, even if they are needed; and
(c) scrolling="yes" ensures that a frame that needs scrollbars will have them, and, depending on the browser, may (MSIE) or may not (Netscape) provide disabled (greyed-out) scrollbars for a frame that doesn't need them, or at least that's what I see on my computer.

(4) The marginwidth and marginheight attributes respectively add horizontal and vertical margin areas between a frame's boundaries and src content area. HTML Goodies' "HTML 4.01: Frames" tutorial alleges, The[se] commands allow you to set margin height and width in pixels or percentages; however, the W3C classifies marginwidth/marginheight as a %Pixels; attribute type, for which percentages are not valid (and not a %Length; attribute type, for which percentages are acceptable). In practice, I find that MSIE acts on marginwidth/marginheight percentage values, but Netscape ignores them.

(5) The noresize attribute is set to either yes or no in the frameset functions but is actually a Boolean attribute. Putting noresize (or perhaps noresize="noresize") in a frame element's start-tag fixes the frame's width and height; if you leave it out, then the frame's interframe borders can be shifted by normal mouse-cursor dragging and dropping.

I myself would have left the scrolling, marginwidth, marginheight, and noresize attributes out of the Script Tips #49-51 Script. I don't like unresizable windows, and I don't like it when document content is pushed to a window's edges, which is what happens when marginwidth and marginheight are set to 0, as is done in the script via the global variables MW and MH (curiously, the W3C asserts that the marginwidth/marginheight value "must be greater than zero"); regarding the scrolling attribute, I see that auto is the default value - I'll take it.

Fortunately, all of the above attributes can be modified, or even deleted, right there in the Fillit/Pastebox boxes; we can similarly change the document title, the nesting of frameset elements, and also the noframes element, bringing us to...

The noframes element

Each frameset function concludes, via the global variable nf, with a noframes element, which specifies content that should be displayed only by user agents that do not support frames or are configured not to display frames, quoting the W3C. The noframes element appears, optionally, in the content model of the frameset element

<!ELEMENT FRAMESET - - ((FRAMESET|FRAME)+ & NOFRAMES?) -- window subdivision-->
<!--As for a regular expression, ? is a quantifier that means "occurs 0 or 1 time(s)."-->

so it would seem that the noframes element should be a child, and not a sibling, of the frameset element and that nf should be placed before the last f (frameset element end-tag) in each frameset function.

And what kind of content are we talking about? The noframes element's own content-model description is somewhat confusing:

<![ %HTML.Frameset; [
<!ENTITY % noframes.content "(BODY) -(NOFRAMES)">
]]>
<!ENTITY % noframes.content "(%flow;)*">
<!ELEMENT NOFRAMES - - %noframes.content; >

Can or cannot the noframes element contain a body element? (The HTML.Frameset entity's replacement text is "IGNORE".) We can sort out the noframes element's content with a frames-disabled browser; in this regard, MSIE straightforwardly allows me to disable frames as follows:
(1) At the bottom of the Edit menu, access the Preferences pane.
(2) On the Web Browser menu, select the Web Content option.
(3) In the Page Content section, uncheck the Show frames checkbox and then click OK.

Disabling frames with MSIE

Having done this, I can confirm that
(a) it's OK if nf follows the last f in each frameset function - nf's "You need a frames capable browser to view this page" text string is rendered without incident - and
(b) the noframes element can contain
(i) a body element and
(ii) the various block and inline elements that can be descendants of the body element,
at least when using MSIE.

(I ran into trouble when I tried to configure Netscape (7.02) to not show frames. To make a long story short, I somehow botched an attempt to import a user_pref("browser.frames.enabled",false) command into my Mozilla prefs.js file; subsequently, I was unable to launch Netscape and an "Error launching browser window:no XBL binding for browser" alert( ) message would pop up - I ultimately had to uninstall and reinstall Netscape. Needless to say, I really need to upgrade my computer.)

Hmmm...a
Too-much-text-in-file message
message is popping up in my "Framed" source file, so perhaps we should finish off the remaining script issues in the next entry.

reptile7

Labels: , ,



Powered by Blogger

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