7.8 Resizing Frames

NN 6, IE 4

7.8.1 Problem

You want a script to adjust the size of one or more frames in a frameset, including resizing a frame to zero width or height to hide a frame.

7.8.2 Solution

Apply a variation of the script shown in Example 7-1 in the Discussion to your frameset (customized with your frameset element's ID). The script goes in the framesetting document and is invoked by a user interface element in one of the frames. For example, a button specification in one of the frames invokes the toggleFrame( ) function as follows:

<button onclick="parent.toggleFrame( )">Hide/Show Navbar</button>

You could also use hyperlinks or linked images to act as clickable triggers for the action.

7.8.3 Discussion

In browsers whose object models support access to all element types (IE 4 and later or NN 6 and later), you can control the values of the cols and rows attributes of the frameset element via properties of the same names. The frameResize.js code library shown in Example 7-1 adjusts only the cols property of a typical two-column frameset. In this case, the width of the lefthand frame is set to zero, effectively hiding the frame from view.

Example 7-1. Frame-resizing functions in frameResize.js
// global to save previous setting
var origCols;
   
// resize lefthand franem
function resizeLeftFrame(left) {
    var frameset = document.getElementById("masterFrameset");
    origCols = frameset.cols;
    frameset.cols = left + ", *";
}
function restoreFrame( ) {
    document.getElementById("masterFrameset").cols = origCols;
    origCols = null;
}
function toggleFrame( ) {
    if (origCols) {
        restoreFrame( );
    } else {
        resizeLeftFrame(0);
    }
}

The code begins by declaring a global variable, origCols, that preserves the original cols attribute setting so it can be used later to restore the previous setting. Next, the resizeLeft( ) function is wired to apply a numeric value (arriving as an argument) to the first of two values of the frameset's cols property. Before doing so, the function stores a copy of the property value in the origCols variable. To return the frameset to its previous state, invoke the restoreFrame( ) function. These two functions are controlled through a master function, toggleFrame( ), invoked from user interface elements located in one or more of the visible frames.

A logical user interface idea behind the frame toggle is to change the text or image signifying the purpose of the click action. For example, when the frame is showing, the label says something like "Hide Navbar"; when the bar is invisible, the label says "Show Navbar." You can do this quite easily on IE browsers, but not Netscape 6 or 7. Here's how to implement the toggleFrame( ) function to work this IE magic:

function toggleFrame(elem) {
    if (origCols) {
        restoreFrame( );
        elem.innerHTML = "&lt;&lt;Hide Navbar";
    } else {
        resizeLeftFrame(0);
        elem.innerHTML = "Show Navbar&gt;&gt;";
    }
}

The button in the frame is:

<button onclick="parent.toggleFrame(this)">&lt;&lt;Hide Navbar</button>

Figure 7-1 shows the two states of the button with respect to the frameset.

Figure 7-1. Hiding and showing a frame in a frameset
figs/jsdc_0701.gif

The reason Netscape 6 and 7 won't let you handle the UI portion so elegantly is that when the frameset resizes itself to the new specifications, the browser automatically reloads the pages (needlessly, in my opinion). Thus, the default value for the UI element reverts to the value as delivered from the server.

To counteract the problemand provide a cross-browser service to all usersyou can preserve the state of the frame's visibility in a cookie, and use the cookie to determine the text of the button (or image URL if you prefer) each time the frame loads. This added work lets the user's preference persist from session to session, thus enhancing the visitor's enjoyment of the site.

Coding for this enhancement has to work around an unfriendly bug in some versions of IE, where an event handler invoking a function in the parent provides incorrect information about the actual target of the event. The solution requires a little bit of indirection, but the result works in all browsers that support W3C DOM syntax.

The page in the frame that always stays visible defines a placeholder span element whose content is filled only if the browser is of a required minimum scriptability:

<span id="togglePlaceholder"></span>

Scripts include a linked cookies.js library (see Recipe 1.9):

<script type="text/javascript" src="cookies.js"></script>

An onload event handler in that frame's page initiates the creation of the UI element for the frame state toggle:

function setToggleUI( ) {
    var label = "<<Hide Navbar";
    if (document.getElementById) {
        if (getCookie("frameHidden") =  = "true") {
            label = "Show Navbar>>";
        }
        var newElem = document.createElement("button");
        newElem.onclick = initiateToggle;
        var newText = document.createTextNode(label);
        newElem.appendChild(newText);
        document.getElementById("togglePlaceholder").appendChild(newElem);
    }
}

Notice that the button element's onclick event handler invokes the local function initiateToggle( ) in the same frame. This indirection allows the accurate event target to be reported in IE. That function reads the target and sends it along to the revised toggleFrame( ) function in the parent frame:

function initiateToggle(evt) {
    evt = (evt) ? evt : event;
    var elem = (evt.target) ? evt.target : evt.srcElement;
    if (elem.nodeType =  = 3) {
        elem = elem.parentNode;
    }
    parent.toggleFrame(elem);
}

In the parent frame (which must also link in the cookies.js library from Recipe 1.9), the toggleFrame( ) function now not only adjusts the cols setting of the frameset, but it also saves the current state value to a cookie and sets the button's label to the new version (since IE doesn't reload the frameset, it needs the new values directly):

function toggleFrame(elem) {
    if (origCols) {
        elem.firstChild.nodeValue = "<<Hide Navbar";
        setCookie("frameHidden", "false", getExpDate(180, 0, 0));
        restoreFrame( );
    } else {
        elem.firstChild.nodeValue = "Show Navbar>>";
        setCookie("frameHidden", "true", getExpDate(180, 0, 0));
        resizeLeftFrame(0);
    }
}

All other functions in the parent stay the same as in the original solution.

That Netscape 6 and 7 (and perhaps later versions, as well) reload the frameset each time you adjust a frame's dimensions argues against implementing gradually expanding or contracting frames. Flicker from each reload may drive users crazy.

7.8.4 See Also

Recipe 7.7 for reading the current pixel dimensions of a frame; Recipe 1.9 for details on the cookies.js library.