reptile7's JavaScript blog
Monday, September 17, 2012
The Quadrophonic Unfurl, Part 2
Blog Entry #264

We return now to our analysis of the Expanding Colored Squares Example of Netscape's Dynamic HTML in Netscape Communicator resource. In today's post we will discuss the example's changeNow( ), expand( ), and contract( ) functions, which apply to all four of the example's colored square-holding layers.

Change it, now

Having detailed the initialization of the topleftblock layer in our previous episode, we are ready to expand and contract the layer's 1 colored square. Both expansion and contraction of the colored square are mediated by a changeNow( ) function, which is triggered by mousing over the square (at least on my computer, mousing over the invisible part of the topleftblock layer does not call the changeNow( ) function).

function changeNow(n) {
    var thislayer = document.layers[n];
    if (thislayer.status == "waitingToExpand") {
        thislayer.src = thislayer.mysource;
        expand(n); }
    else if (thislayer.status == "waitingToContract") {
        contract(n); }
    return false; }
<layer id="topleftblock" ... onmouseover="changeNow(0);"> ... </layer>

As for the initializeTopLeft( ) function discussed in the previous post:
(i) The topleftblock layer's document.layers[i] index, 0, is passed to the changeNow( ) function.
(ii) The 0 index is given an n identifier.
(iii) n is plugged into a document.layers[ ] expression to get the topleftblock layer.
(iv) The topleftblock layer is given a thislayer identifier.
These operations were unnecessary for the initializeTopLeft( ) function but we need them or their equivalent here because the example's other document.layers also call on the changeNow( ) function for the expansion and contraction of their visible regions; they can be simplified by
(a) passing a this object reference to the changeNow( ) function and then
(b) giving the this argument a thislayer identifier.

Next, an if statement checks if thislayer has a waitingToExpand status: check. Subsequently:

(1) The topleftblock layer's child layer holding the 1 numeral is overwritten with the point1.htm file by assigning thislayer.mysource to thislayer.src. Reason #2 why the colored squares are not housed in divs: the widths of div-based layers do not hold when external files are loaded into them, as we observed two entries ago. FYI: This assignment constitutes a load event and therefore re-calls the initializeTopLeft( ) function, which then clips the topleftblock layer a second time - that's why this operation is carried out now and not post-expansion.

(2) An expand( ) function is called and passed the n index.

If thislayer's status were waitingToContract (vide infra), then a contract( ) function would be called and passed the n index. The changeNow( ) function concludes with a return false; statement whose purpose eludes me - indeed, commenting it out has no effect as far as I can tell.

Square expansion

The expand( ) function expands the topleftblock layer's 1 colored square leftward and upward à la the squaresdemo0 demo of the previous post.

function expand(n) {
    var thislayer = document.layers[n]; ...

The expand( ) function begins by...ah, we don't need to go through this again, do we? Suffice it to say that we could pass the thislayer reference itself to expand( ) in the same way and then use the thislayer argument in the statements that follow.

thislayer.status = "expanding";

Before expanding the 1 square, the expand( ) function first sets thislayer's status to expanding. Now, if the user mouses over the square and lets the mouse cursor sit until the square has finished expanding - and that's what the overwhelming majority of users are going to do - then the above statement is unnecessary. However, it is possible (but highly unlikely, given how quickly the square expands) that the user might instead wildly move the mouse cursor around as the square is expanding. Suppose the user darts the mouse cursor in and out of the square during the expansion: this would result in spastic square motion if thislayer's status were still waitingToExpand as changeNow( ) would be re-called, point1.htm would be reloaded into thislayer, initializeTopLeft( ) would be re-called and thislayer would be clipped again, expand( ) would be re-called, etc. - the expanding status is meant to choke off this chain of events.

Let's get expanding, shall we?

thislayer.clip.left = thislayer.clip.left + thislayer.dleft; = + thislayer.dtop;
thislayer.clip.right = thislayer.clip.right + thislayer.dright;
thislayer.clip.bottom = thislayer.clip.bottom + thislayer.dbottom;

Per the dleft, dtop, dright, and dbottom values set by the initializeTopLeft( ) function (-delta, -delta, 0, and 0, respectively), thislayer's clip.left and values drop by 10 and its clip.right and clip.bottom values remain unchanged, giving a 60px-by-60px square that doesn't show any of the point1.htm content yet:

The example display after the first expansion of the topleft colored square

The preceding statements can be written more compactly via the += assignment operator:

thislayer.clip.left += thislayer.dleft; += thislayer.dtop; ...

More generally, in all four initialization functions the dleft/dtop/dright/dbottom (d*) property definitions are such that the properties expand the colored squares when they are added to their matching clip.* properties; as a result, the toprightblock, bottomleftblock, and bottomrightblock layers are also able to use the expand( ) function for their square expansions.

Expansion is continued or not via the following conditional:

/* Is the layer fully contracted? True if: The square is the top OR bottom left AND its clip left is less than or equal to the minimum clip for contracted squares OR the square is the top OR bottom right AND its clip right is greater than or equal to the max clip for contracted squares. */

if ((((thislayer.myposition == "topLeft") | (thislayer.myposition == "bottomLeft")) && (thislayer.clip.left >= minclip)) ||
(((thislayer.myposition == "topRight") | (thislayer.myposition == "bottomRight")) && (thislayer.clip.right <= maxclip)))
    window.setTimeout("expand(" + n + ");", 50);
else thislayer.status = "waitingToContract";

• The conditional is preceded by a problematic and poorly worded comment; assuming that we want the colored square to fill the original topleftblock layer and not go beyond that, the comment should say, "Is the square not fully expanded? True if the square is held by the topLeft or bottomLeft layer and the layer's clip.left value hasn't hit 0 yet..."

• The topleftblock layer can be flagged here by a == "topleftblock" comparison - it is not necessary to use a custom property to do so.

• Oddly, the thislayer.myposition == "topLeft" and thislayer.myposition == "bottomLeft" subconditions are boolean-wise compared via the | bitwise operator as opposed to the || logical operator. This turns out to not be a problem: although the | operator expects number operands, the true and false booleans respectively convert to 1 and 0 in the context of a | operation. For the topleftblock layer, 1 | 0 returns 1.

• Not previously discussed, the minclip operand appearing in the thislayer.clip.left >= minclip subcondition is a global variable that is set to 0 prior to the initializeTopLeft( ) function.

• The if clause is operative as long as the thislayer.clip.left >= minclip subcondition returns true (1 && true returns true). The subcondition's use of the >= operator means that the subcondition returns true when the colored square has filled the topleftblock layer; as a result, one last expand( ) run occurs, thislayer's clip.left and values end at -10 and not 0, and the fully expanded square measures 210px by 210px. To the extent that this bothers you, stopping the square at the original layer boundaries is no more difficult than replacing >= with >.

• The setTimeout( ) command can alternately be written as window.setTimeout(expand, 50, n);. (This syntax doesn't work with IE, but then again, the DHiNC examples weren't meant for IE in the first place.) If you pass a thislayer argument to the expand( ) function, then you'll have to use the window.setTimeout(expand, 50, thislayer); syntax as window.setTimeout("expand(" + thislayer + ");", 50); throws a missing ] after element list error for a reason beyond my understanding.

• As determined by the above code, the expand( ) expansion loop runs for 16 iterations. When thislayer.clip.left and reach their final value(s), the else clause fires and sets thislayer's status to waitingToContract.

Here's the final result:

The example display when expansion of the topleft colored square is complete

Other square expansions

(2) The 2 square expands upward and rightward: for the parent toprightblock layer, decreases, clip.right increases, and clip.left and clip.bottom do not change.

(3) The 3 square expands leftward and downward: for the parent bottomleftblock layer, clip.left decreases, clip.bottom increases, and and clip.right do not change.

(4) The 4 square expands rightward and downward: for the parent bottomrightblock layer, clip.right increases, clip.bottom increases, and clip.left and do not change.

Square contraction

Mousing out from a fully expanded colored square and then mousing over the square causes the square to contract to its original state via the aforementioned contract( ) function. Returning to the

The fully expanded topleft colored square

colored square, the contract( ) function first gets the topleftblock layer and then sets its status to contracting in order to head off any mouseover mischief (vide supra).

function contract(n) {
    var thislayer = document.layers[n];
    thislayer.status = "contracting"; ...

In the contract( ) function, contraction is carried out by subtracting thislayer's d* properties from its clip.* properties:

thislayer.clip.left = thislayer.clip.left - thislayer.dleft; = - thislayer.dtop;
thislayer.clip.right = thislayer.clip.right - thislayer.dright;
thislayer.clip.bottom = thislayer.clip.bottom - thislayer.dbottom;

Yes, these statements are good for all four document.layers; yes, we could be using the -= operator here. For the topleftblock layer, clip.left and increase by 10* and clip.right and clip.bottom remain unchanged, giving a 200px-by-200px square that fills (no longer overflows) the original layer.
(*These operations merely shave off excess margin-left and margin-top generated by the expand( ) function - I trust that I don't need to show you a picture of the resulting square.)

Contraction continues via the following if statement:

if ((((thislayer.myposition == "topLeft") | (thislayer.myposition == "bottomLeft")) && (thislayer.clip.left < minclipcontracted)) ||
(((thislayer.myposition == "topRight") | (thislayer.myposition == "bottomRight")) && (thislayer.clip.right > maxclipcontracted)))
    window.setTimeout("contract(" + n + ");", 50);

Not previously discussed, the minclipcontracted operand appearing in the thislayer.clip.left < minclipcontracted subcondition is a global variable that is set to 50 prior to the initializeTopLeft( ) function. The thislayer.clip.left < minclipcontracted subcondition means that thislayer.clip.left and only get as far as 50

The example display when thislayer.clip.left is equal to minclipcontracted

before the square snaps back to its original state via an accompanying else clause, i.e., thislayer.clip.left and do not smoothly increase over the 100 pixels separating each pair of minclipcontracted and maxclipcontracted boundaries. Here's that else clause:

else {
    thislayer.status = "waitingToExpand";
    thislayer.document.close( ); }

The else clause first sets thislayer's status to waitingToExpand even though we're not done contracting yet. Next, a write( ) command writes thislayer's original mytext content - the <layer top='160' left='168'><h1>1</h1></layer> child layer - to thislayer's document, thereby overwriting the point1.htm content; the write( ) command does not need to be preceded by an open( ) command but it does need to be followed by a close( ) command. (Unlike IE 4.x, Netscape 4.x does not support the innerHTML property, so we have to go about this writing business, which is documented in the Writing Content in Positioned Blocks section of the DHiNC resource, somewhat more circuitously.) The close( ) operation constitutes a load event and consequently re-calls the initializeTopLeft( ) function, which finishes the contraction by reclipping the topleftblock layer to its original state.

In sum, the contract( ) function runs only 6 times and it does less than half of the actual contracting, although you can change that by deploying thislayer.clip.left < maxclipcontracted and thislayer.clip.right > minclipcontracted subconditions in the above if condition.

I trust that you can handle the other document.layers colored square contractions at this point.

Our next task is to get this thing to work with modern browsers, and we'll get cracking on it in the following entry.

Comments: Post a Comment

<< Home

Powered by Blogger

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