reptile7's JavaScript blog
Thursday, August 23, 2012
 
I Can't Choose Either, Norah
Blog Entry #262

Welcome back to our discussion of the Nikki's Diner Example: in today's post we move from theory to practice.

showSpecials( ) execution

As it happens, the two example versions do not work equally well - the subsections below detail what I see when using Netscape Communicator 4.61 in the SheepShaver environment.

dinercss.htm

The dinercss.htm document specifies a width:400px; style declaration for the menulayer div. When the sat.htm file initially loads into the menulayer div, the menulayer display does have a width of 400 pixels - so far, so good.

The initial menulayer display

Upon choosing an other-than-Saturday option from the menu1 selection list - say, the Tuesday option - the corresponding specials/ file (tues.htm) loads into the menulayer block but the width of the block increases to 100%, i.e., the block's width spans the width of the viewport.

The menulayer display upon choosing the Tuesday option

With respect to its identity as a layer, the menulayer block's clip.width property now returns 816. With respect to its identity as a div, the menulayer block's document.ids.menulayer.width style width continues to read 400px, as though the tues.htm content were overflowing the div. Weird, huh? But such are the vicissitudes of working with primitive browsers like Netscape 4.x.

Setting the menulayer clip.width (Netscape did not equip the layer object with a 'normal' width property) to 400 cuts off the right half of the display - the layer width does not shrink to 400 pixels.

diner.htm

There are no problems with this version: as for the dinercss.htm version, the showSpecials( ) function smoothly loads the specials/ files into the specials layer, but the menu layer's width="400" attribute faithfully keeps the layer/display width at 400 pixels. FYI: Netscape 4.x does not support the width attribute for the div element.

Calling Dr. Iframe

With an eye on getting the example to work with modern browsers, the obvious choice of elements to replace the menu layer is an iframe, you know, the thing I used to use for my demos on this blog (including the demo at the end of the post) back when I had EarthLink server space at my disposal. The iframe element has a src attribute for importing an external document and width and height attributes for bounding the imported content. As for the menu layer's left="50" attribute - no standard HTML elements have a left attribute - an iframe can be CSS-offset like any other element.

#menu { position: relative; left: 50px; }
...
<iframe id="menu" width="410" height="425" src="specials/sat.html">It seems that your browser does not support iframes. Please call Nikki's Diner at 123-4567 for today's specials.</iframe>


• In the absence of a height setting (the menu layer doesn't have a height attribute), the iframe-supporting browsers on my computer give an iframe a default height of 150 pixels, which is well short of what we want.

• By default, a layer does not have a border whereas an iframe has an inset 2px border. IMO a border neither adds to nor detracts from the display, but you can lose the iframe border with a frameborder="0" attribute if you don't like it.

• If an iframe's src content overflows the iframe viewport, then the browser will automatically equip the iframe with scrollbars so that the user can access the beyond-the-viewport content; you can mandate that an iframe not have scrollbars via a scrolling="no" attribute, but it is generally a good idea to allow for scrollbars in case they are needed. The above width="410" and height="425" settings eliminate content overflow for all of the specials/ pages if the browser's Preferences... default font size is 16px or smaller; push the font size to 18px and you'll get a vertical scrollbar.

• The iframe can be absolutely or relatively positioned, the difference being that in the relative case you also get 8px of default margin-left indentation and in the absolute case you don't.

• Something that is true for modern browsers but not older browsers, and is a bit surprising given that an iframe is a replaced element: if the specials/ pages did not have background colors (they all do, although some of those colors don't go very well with black text), then a #menu { background-color: <color>; } style rule in the main document would impart a background color to the pages. N.B. The pages' paragraphs and h2 headings cannot be styled in this way, but these elements can be styled from the main document with a bit of JavaScript, as we'll see below.

Before moving on:
Quirksmode reports that iframes can be disabled with IE 9, Firefox, and Opera. Upon applying the Firefox and Opera disablements to the above iframe element (as a Mac user I can't tell you how the IE disablement shakes out), I find that
(a) Firefox completely removes the element - both its specials/ src content and its It seems that your browser does not support iframes... content string - whereas
(b) Opera removes the src content and renders the content string, as you would expect.

Oh, in case you were wondering, iframes are not supported by Netscape 4.x.

showSpecials( ) retool

Do we need to use a switch statement to map the user's chosen menu1 option onto its corresponding specials/ file? Nah...

var dayArray = ["sat", "sun", "mon", "tues", "wed", "thurs", "fri"];
function showSpecials(n) {
    var specials = document.getElementById("menu");
    specials.src = "specials/" + dayArray[n] + ".html"; }


Set up a dayArray Saturday-to-Friday array of specials/ day abbreviations, get the menu iframe, concatenate specials/ and dayArray[n] and .html, and assign the resulting string to the src property of the menu/specials iframe object, and you're good to go.

Styling the specials/ text

The diner.htm document applies a tags.P.marginLeft = 50; style statement to the paragraphs of the specials/ files whereas the dinercss.htm document applies a p { margin-left: 10%; margin-right: 10%; } style rule set to those paragraphs; these stylings work as advertised notwithstanding that the specials layer is again a replaced element.

Without any CSS, the specials/ paragraphs get 8px of margin-left indentation. Do they need to be further 'marginized'? Not really, but suppose you wanted to push them rightward by 10 pixels. You could add a p { margin-left: 10px; } style rule to each specials/ document; however, through the magic of the contentDocument attribute of the HTML DOM's HTMLIFrameElement interface, which accesses the src document held by an iframe, we can style the paragraphs in one go from the main document as follows:

function formatIframe( ) {
    var specials = document.getElementById("menu");
    var slideParas = specials.contentDocument.getElementsByTagName("p");
    for (i = 0; i < slideParas.length; i++) slideParas[i].style.marginLeft = "10px"; }


The documents' h2 elements can be scooped up and centered

for (i = 0; i < slideH2s.length; i++) slideH2s[i].style.textAlign = "center";

in an analogous manner.

The formatIframe( ) function should be called whenever a new specials/ document has loaded into the menu iframe; we accordingly trigger the function via an onload="formatIframe( );" iframe element attribute.
(A corresponding document.getElementById("menu").onload = formatIframe; statement is buggy with Opera - there's no offset for the first load but things are OK for subsequent loads - so I encourage you to stick with the element attribute approach.)

Uh-oh - our friends at W3Schools warn:
Note: Internet Explorer 8 (and higher) supports the contentDocument property only if a !DOCTYPE is specified. For earlier versions of IE, use the contentWindow property.
The contentWindow property references a frame|iframe directly as a window object (as opposed to an object implementing the Core DOM's Element interface); regarding the formatIframe( ) function, specials.contentWindow.document would be equivalent to specials.contentDocument. Excepting IE 5.2.3, the contentWindow property is supported by all of the OS X GUI browsers on my computer - Netscape 7 also supports it. The contentDocument and contentWindow members are both now part of the HTML5 HTMLIFrameElement interface.

It subsequently occurred to me that we could access the iframe window and its document in a more basic and general way via the window.frames collection, i.e., window.frames["menu"].document would be equivalent to specials.contentDocument. The frames member is now part of the HTML5 Window interface.

Either the contentWindow approach or the window.frames approach will be fine for most if not all users; however, the conditional below should allow you to reach anyone who falls through the cracks:

if (specials.contentDocument) iframeDocument = specials.contentDocument;
else if (specials.contentWindow) iframeDocument = specials.contentWindow.document;
else if (window.frames) iframeDocument = window.frames["menu"].document;


You're just an object, part 2

The Notes on embedded documents section of the HTML 4.01 Specification shows how to recast an iframe element as an object element.

<object id="menu" width="410" height="425" data="specials/sat.html">sat.html is not available, blah blah blah...</object>

As shown above, the data attribute of the object element plays the role of the src attribute of the iframe element.

Changing the specials.src = "specials/" + dayArray[n] + ".html"; showSpecials( ) statement to specials.data = "specials/" + dayArray[n] + ".html"; should be all we need to do to use the menu object with the Nikki's Diner Example. In the event, I find that Chrome and Safari will not write the specials.data property (i.e., you're stuck with the Saturday display); Firefox and Opera will write it but the file-to-file transitions are less smooth than when writing the specials.src property for an iframe. So for importing the specials/ documents into the main document, an iframe is clearly the way to go.

Demo

January 2017 Update: The restored demo below does not in fact leverage an external set of specials/*.html files but instead document.write( )s the *.html contents from scratch - I refer you to the source of this page for the details.


Welcome to Nikki's Diner!



Nikki's Diner is the best place for vegan food in Netscapeville.

You can find us at the corner of Communicator Street and Navigator Way. We're open from 10 am to 6 pm every day. We don't take reservations, so just come on down. We guarantee that after you visit us once, you'll be back on a regular basis!

We have an extensive regular menu of tasty meals in addition to our daily specials.

You can use the following menu (no pun intended) to view the Specials for any day this week. Our specials change every week.

Please select a day of the week:


I'll bet your mouth is watering for some Tofu, Artichoke, and Asparagus Surprise right about now - mmm, mmm, sounds scrumptious, doesn't it? As for myself, I think I'll mosey on over to the po-boy shop down the street, where they've got a smoked sausage on french - dressed, no mayo, con cebolla - with my name on it...

Our next DHiNC project, the Expanding Colored Squares Example, is the most complicated of the lot, and we'll tuck into it in the following entry.

Saturday, August 11, 2012
 
Cashew and Apple Risotto
Blog Entry #261

In today's post we will check over the Nikki's Diner Example of Netscape's Dynamic HTML in Netscape Communicator resource. The Nikki's Diner Example does pretty much what the Fancy Flowers Farm Example (see Blog Entries #256/#257) does, namely, it displays one of several blocks of content at a specific position on the page in response to choosing a selection list option. The Nikki's Diner Example complements the Fancy Flowers Farm Example in the following respects:

(1) The Nikki's Diner Example loads its blocks of content into a common layer placeholder and therefore more resembles a traditional slide show than does the Fancy Flowers Farm Example, which places each block of content in a separate layer.

(2) The Nikki's Diner Example initially places its blocks of content in individual HTML files that are external to the main document, whereas the Fancy Flowers Farm Example's content blocks are all located in the main document.

(3) The Nikki's Diner Example imports its blocks of content via the layer object's src property, for which the example serves as a showcase, whereas each Fancy Flowers Farm Example content layer is displayed by turning on its visibility setting.

The Nikki's Diner Example methodology could be applied to the Fancy Flowers Farm Example, and vice versa.

Netscape offered two versions of the Nikki's Diner Example: (1) a diner.htm version that uses an ilayer element to position the UI and a layer element to host the aforementioned blocks of content and (2) a dinercss.htm version that uses div elements for these purposes. The JavaScript parts of the diner.htm and dinercss.htm documents differ by a single layer identifier but are otherwise identical.

The main page and its layout

The Nikki's Diner Example codes a Web page for Nikki's Diner, the best place for vegan food in Netscapeville. The top part of the page textually comprises a "Welcome to Nikki's Diner!" h1 heading and a series of paragraphs that gives information about the diner. After that we have the business end of the page, specifically a menu1 selection list for choosing a day of the week and a space for displaying the diner's entree and dessert specials for the chosen day.

The day-of-the-week selection list

<form name="form1">
<select name="menu1" onchange="showSpecials(this.selectedIndex); return false;">
<option>Saturday</option>
<option>Sunday</option>
<option>Monday</option>
<option>Tuesday</option>
<option>Wednesday</option>
<option>Thursday</option>
<option>Friday</option>
</select></form>


is wrapped in a form, which is needed for Netscape 4.x but is unnecessary for modern browsers. (No use is made of the form's name, BTW.) The form is preceded by a

p.plainPara { margin-left: 0px; }
...
<p class="plainPara">Please select a day of the week:</p>


paragraph that could be marked up as a label, if desired.

The space for displaying the diner's entree/dessert specials is framed by either a layer element

<layer name="menu" left="50" width="400" bgcolor="#bbffbb" src="specials/sat.htm"></layer>

or a div element

#menulayer { position: absolute; left: 20pt; width: 400px; include-source: url("specials/sat.htm"); }
...
<div id="menulayer"></div>


as noted in the post's introduction.
(As the menu layer has no children, its bgcolor attribute serves no purpose and can be removed - if the sat.htm file were not available, you wouldn't see any #bbffbb color on the page.)

When the main page loads, the menu layer or menulayer div is charged with the Saturday specials

The Saturday specials at Nikki's Diner

so as to coordinate it with the menu1 selection list, whose first-in-source-order, 'preselected' option is the Saturday option. The Saturday data is set in the layer case via a src="specials/sat.htm" element attribute and in the div case via an include-source: url("specials/sat.htm"); style declaration - see the SRC and source-include (sic) subsection of the DHiNC resource. N.B. The correct formulation is include-source, not source-include.

In the diner.htm document the form1 form and its preceding paragraph are wrapped in an

<ilayer left="50"></ilayer>

- the ilayer element is briefly discussed here in the DHiNC resource - in order to indent them by 50 pixels; the menu layer is also left="50"-indented. In the dinercss.htm document the form1 form and its preceding paragraph are wrapped in a

#formlayer { position: relative; left: 20pt; }
...
<div id="formlayer"></div>


in order to indent them by 20 points (ca. 7 millimeters), and the menulayer div is also left:20pt;-indented. (Why not a 50px offset? Your guess is as good as mine.) The ilayer is part of the diner.htm document's normal flow but the menu layer is not, and consequently these elements are not quite left-aligned because the ilayer indentation also includes 8px of default margin-left given to it by the browser; ditto for the formlayer and menulayer divs.

By itself, an ilayer establishes an inline formatting context, but if its first child and last child are block-level elements*, as is the case in the diner.htm document, then it establishes a block formatting context; as a result, the menu layer appears below the ilayer in the absence of a top setting. On the dinercss.htm page, the menulayer div appears below the formlayer div per the normal block-level rendering of the div element.

*Most standard %inline; HTML elements cannot contain block-level elements but there are four that can: button, iframe, map, and most importantly object, whose generic embedded object description roughly applies to an ilayer.

The specials/ files

In the directory holding the diner.htm|dinercss.htm file is a specials/ subdirectory containing seven files: sat.htm, sun.htm, mon.htm, tues.htm, wed.htm, thurs.htm, and fri.htm. Each specials/ file is a complete HTML document whose body lists four entree specials and two dessert specials for a given day of the week; for example, here's the sat.htm document body, which spells out the Saturday specials:

<body bgcolor="#bbee99">
<hr><h1 align="center">Saturday</h1><hr>
<h2 align="center">Entrees</h2>
<p>Curried Tofu with Bean Sprouts and Raisins</p>
<p>Peanut and Coriander Risotto</p>
<p>Casserole of Potato, Lemon Tyme, and Sundried Tomatoes</p>
<p>Chef's Special Nutloaf</p>
<h2 align="center">Desserts</h2>
<p>Orange and Mint Sorbet</p>
<p>Banana Fool</p>
</body>


(The bgcolor attribute of the body element and the align attribute of the h# elements are deprecated, but you knew that, right? I trust you can write out their CSS replacements.)

The specials/ files are loaded into the menu layer or menulayer div by a showSpecials( ) function, which is called by changing the selection state of the menu1 selection list.

The showSpecials( ) function

The dinercss.htm showSpecials( ) function is given below; in the diner.htm document the var specials = document.menulayer; statement is cast as var specials = document.menu; but apart from that there is no difference between the functions.

function showSpecials(n) {
    var specials = document.menulayer;
    switch (n) {
        case 0: specials.src = "specials/sat.html"; break;
        case 1: specials.src = "specials/sun.html"; break;
        case 2: specials.src = "specials/mon.html"; break;
        case 3: specials.src = "specials/tues.html"; break;
        case 4: specials.src = "specials/wed.html"; break;
        case 5: specials.src = "specials/thurs.html"; break;
        default: specials.src = "specials/fri.html"; } }


The menu1 selection list initially shows the Saturday option; choosing another option calls the showSpecials( ) function and passes thereto this.selectedIndex, which is given an n identifier.

The first showSpecials( ) statement gets the menulayer|menu layer and gives it a specials identifier. Subsequently a switch statement assigns one of the specials/ files to the src of the specials layer. For example, if we choose the Tuesday option, for which n is 3, then the case 3: specials.src = "specials/tues.html"; break; clause is operative and tues.htm is loaded into the specials layer. The break statement at the end of the clause prevents fallthrough to the subsequent clause and terminates the execution of the switch statement.

The Friday option should be handled by a case 6 clause, and not a default clause, which is meant for 'no match was found' situations.

We'll finish our Nikki's Diner Example discussion in the following entry, specifically I'll tell you how the original example works with Netscape 4.x and then give you my own example code/demo.

Thursday, August 02, 2012
 
Schindleria Præmatura
Blog Entry #260

In today's post I will detail my fishdemo2 approach to the Swimming Fish Example's Changing the Stacking Order of Fish and Poles animation and how it differs from the example's fish2css.htm approach.

HTML

Delayerization

I find that Netscape 4.x straightforwardly applies the CSS position property to textual elements (paragraphs, headings, links, etc.) and to the div and span container elements but that its application of position to more complex elements is problematic to a greater or lesser extent. For example, Netscape 4.x will position a ul or ol list but upon doing so the resulting list item markers are invariably discs, regardless of the list's type setting.

Apropos the Changing the Stacking Order animation, with Netscape 4.x:
(1) position/top/left declarations have no effect at all on img elements;
(2) a form { position: absolute; top: 100px; left: 10px; } rule set bizarrely causes the button-containing form to disappear (poof, gone).
This is why the fish and pole images and the interface form are wrapped in layer or span containers. Fortunately, the current, standard CSS 2.1 position property applies to all elements and is supported by all modern (and more than a few not-so-modern) GUI browsers, and I have accordingly sent the layer/span wrappers packing and transferred their ids/styles to their children with the exception of the fishB span/img, which I've thrown out altogether.

A new interface

I have added to the animation UI a button that when clicked stops the fish's movement; updated code for this button appears at the end of the post. (Of course, some users may want to watch the fish swim indefinitely. ;-)) Moreover, I've traded in the parent form of the and buttons for a more semantically appropriate div container. Are we submitting anything to a processing agent? We're not submitting anything to a processing agent.

Netscape 4.x won't render controls outside of a form and doesn't support the button element, but again, we're not coding for Netscape 4.x anymore.

Validation note

A body element with img element children will pass a transitional validation but not a strict validation.
• In the Transitional DTD, the body element has a (%flow;)* +(INS|DEL) content model.
• In the Strict DTD, the body element has a (%block;|SCRIPT)+ +(INS|DEL) content model.
If you want to carry out a strict validation of either Swimming Fish Example animation, then you'll have to wrap the fish and pole images in a div, and I've done that in the fishdemo2 code. Or you could just do a transitional validation. The necessary document type declarations for these validations are detailed here.

JavaScript

fish2.gif preload

If we've chucked the fishB span/img, then how are we going to preload the fish2.gif image? This can and should be done the traditional way, i.e., via an Image object constructor:

var fishB = new Image( );
fishB.src = "fish2.gif";
/* An analogous fishA object can be created for the fish1.gif image: */
var fishA = new Image( );
fishA.src = "fish1.gif";


Controlling properties redux

The fish2css.htm JavaScript uses five functions to move the fish layer to and fro and thread it through the pole layers: initializeFish( ), movefish2( ), changeDirection( ), changePoles( ), and resetPoles( ). The linchpin of this functionality is the fish layer's custom direction property, which indirectly determines
(1) the fish layer's direction of movement,
(2) the source of the image held by the fish layer, and
(3) the z-stacking order of the fish and pole layers.

In the course of considering how to consolidate the changePoles( ) and resetPoles( ) functions, it occurred to me that we could 'cut out the middleman' in that (2) the src of the fish image, now held by an id="fish2" img element, could be used to determine (1) the fish2 img's direction of movement and (3) the z-stacking order of the fish2/pole imgs. Toward this end I defined a boolean isForwardFish variable that evaluates to true if the swimming fish is fish1.gif and to false if the swimming fish is fish2.gif.

var fish2 = document.getElementById("fish2"), isForwardFish;
function movefish2( ) {
    isForwardFish = /fish1/.test(fish2.src); ... }


The isForwardFish definition compares a fish1 regular expression literal with the fish2.src string via the test( ) method of the RegExp object. Alternatively, an equivalent comparison of fish2.src and the string fish1 can be carried out via the indexOf( ) method of the String object:

isForwardFish = fish2.src.indexOf("fish1") != -1;

Stack it

With the isForwardFish flag in hand, we can use the ?: conditional operator to recast the resetPoles( )/changePoles( ) functionality as:

var bluepole2 = document.getElementById("bluepole2");
var greenpole2 = document.getElementById("greenpole2");
var redpole2 = document.getElementById("redpole2");
function movefish2( ) { ...
    bluepole2.style.zIndex = isForwardFish ? "1" : "4";
    greenpole2.style.zIndex = isForwardFish ? "2" : "3";
    fish2.style.zIndex = isForwardFish ? "3" : "2";
    redpole2.style.zIndex = isForwardFish ? "4" : "1"; ... }


• Unlike the layerObject.moveAbove( ) method, the CSS z-index property has (and has long had) cross-browser support.

• The zIndex variant of the z-index property is not part of CSS but is actually an attribute of the Style DOM's CSS2Properties interface; the style object itself implements the Style DOM's CSSStyleDeclaration interface.

• If isForwardFish is true, then the above zIndex statements produce the same z-stacking order as that resulting from the fishdemo2 source, i.e., the blue pole is on the bottom, the green pole is above the blue pole, the fish is above the green pole, and the red pole is on top; if isForwardFish is false, then the z-stacking order is reversed.

• All CSS2Properties attribute values have a DOMString type and are typed as strings regardless of what you assign to them by most JavaScript engines; in practice, integers assigned to zIndex can be stringified or not, per your preference.

Right and left we go

In conjunction with the fishA/fishB Image objects, the isForwardFish flag also allows us to throw out the changeDirection( ) function and formulate the fish movement code as:

fish2.style.left = "0px";
var leftnumber2, timerID2;
function movefish2( ) { ...
    leftnumber2 = parseInt(fish2.style.left, 10);
    if (isForwardFish) {
        if (leftnumber2 < 450) fish2.style.left = (leftnumber2 + 5) + "px";
        else fish2.src = fishB.src; }
    else {
        if (leftnumber2 > 10) fish2.style.left = (leftnumber2 - 5) + "px";
        else fish2.src = fishA.src; }
    timerID2 = window.setTimeout("movefish2( );", 30); }


With the fishA.src and fishB.src expressions respectively replacing the original fish.forwardimg and fish.backwardimg pointers and with isForwardFish replacing fish.direction, we obviously don't need the initializeFish( ) function either.

In sum, we don't need five functions to run the Changing the Stacking Order animation: a single movefish2( ) function based on the source of the fish image and supported by a small set of preceding global statements will do the job.

In the name of completeness

Once the fish movement is under way, reclicking the button will (a) trigger a second movefish2( ) cycle and (b) approximately double the speed of the fish because the fish2.style.left assignments will be executed twice as often per 30-millisecond interval; additional clicks further speed up the fish. You can limit the fish movement to a single movefish2( ) cycle as follows:

(1) Globally declare an isMoving variable and set it to true at the beginning of the movefish2( ) function body.

var isMoving; function movefish2( ) { isMoving = true; ... }

(2) Code the animation UI as:

#buttondiv { position: absolute; top: 100px; left: 10px; }
...
<div id="buttondiv">
<button type="button" onclick="if (!isMoving) movefish2( );">Move the fish</button>
<button type="button" onclick="window.clearTimeout(timerID2); isMoving = false;">Chill out, fish</button>
</div>


The !isMoving condition returns true for the first click but false for subsequent clicks, thereby stopping the latter dead in their tracks.

In the following entry we'll move on to the Nikki's Diner Example of the DHiNC resource.


Powered by Blogger

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