Manipulating Frames with VBScript and ASP

The first two examples in this chapter showed some things that can be done easily with client-side scripting. However, they didn't actually incorporate any of the features of ASP, we only suggested ways that this could be done. In this and the next examples, we'll be using ASP in conjunction with script code on the client.

While ASP is great at building the individual pages for a web application dynamically, its features are rather limited when it comes to handling the frameset pages that are so common to today's sites. Using the Redirect method of the Response object we can redirect to a single page, but we can't target it to a specific frame—only the browser can do that. The combination of client and server code in this example shows how scripting languages on both sides of the Web connection can, together, manage framesets and the pages inside the frames.

The Frames Example

This is more complicated than the first two examples we looked at. Before we launch into an explanation of what this example does, you might like to bring up frameset.asp in your browser and see for yourself. Experiment by clicking on a few of the links in the left hand pane. We'll spend some time discussing the code that makes this sample work, and you'll understand it better if we're familiar with the samples behavior in the browser. Since this is now an ASP file, you also need to be sure that it comes from your server and is loaded via HTTP.

You'll notice that clicking on a page link on the left hand side loads that page into the right hand frame, and also updates the left frame itself so that only links to the pages not currently shown are available. This makes sense—if we're already viewing the second page we shouldn't be able to jump to it again.

Since we're viewing page two in the right hand frame, it's not visible in the list in the left hand frame.

Now click over to the third page, and you'll see a list of options in the main frame. If you choose one of the Option links, rather than a Page link, you'll see the left frame refresh with a list of the remaining options, and the right frame with the actual page you selected. If you watch closely, you'll see that the left frame is refreshed a split second before the right frame is updated. We'll talk about why this happens later in this section.

So that's what this example does: it displays a variety of pages in the main section of a two-pane frameset and keeps the left-hand pane in sync—and it uses both client and server-side scripting to accomplish this.

If you've designed a site with frames before, you know that creating a two-pane frameset with a navigation frame and contents frame isn't too difficult. The frameset HTML file contains the size and URL information for the frames, so that they can be created and laid out. Each of the pages is a separate file, as specified in the frameset file, and these are loaded into the frames just created.

Once the pages have been rendered, new pages are targeted to a specific frame by adding the TARGET attribute to the links that modify the other frame. For example, our frameset has two frames called NavBar and MainFrame. To change the contents of MainFrame from a click on a link in NavBar, we'd just use:

<A HREF="mypage.htm" TARGET="MainFrame">Go To My Page</A>

However, with this method, the left-hand frame doesn't change to reflect what is shown in the main frame. Our example does allow this, and this significant improvement comes from the combination of ASP and client-side code.

Where would we use something like this? The answer is any place that we currently use (or could use) frames, but want to give the viewer of our site more feedback as to what is happening. Or perhaps we'd like to customize the available options based on which user is accessing our site, or what part of the site they're viewing. We can easily extend this example to the specific needs of our site, but first we should understand how the code works.

How It Works

In contrast to the first two examples (and the next one), this sample consists of more than a few files, although the interesting work takes place primarily in two files: frameset.asp and navbar.asp. It's no coincidence that these files are the only Active Server pages of the whole lot. The rest of the pages are simple HTML files—that in the real, non-book-example, world would hold the site's content. So we can keep things straight, all of the files are listed below:

Filename Purpose
frameset.asp The top-level frameset page
navbar.asp Navigation bar (left-hand pane) page
pageone.htm Page one
pagetwo.htm Page two
pagethree.htm Page three (with option links)
option1.htm Option page one
option2.htm Option page two
option3.htm Option page three
option4.htm Option page four

Creating the Frameset with ASP

We first need to understand how the frameset.asp page works. This is the page you loaded into your browser earlier, and it's where everything starts. The ASP code in the page sets the values of two variables, NavBarPage and MainFramePage, depending on the value passed to the page in the URL. We'll see this in a while—just accept for now that they are set up correctly. So, frameset.asp sets up the frameset with different pages:

... the ASP code that sets NavBarPage and MainFramePage goes here ...
<TITLE> Frameset Demonstration </TITLE>
   <FRAME NAME="NavBar" SRC="<%= NavBarPage %>" SCROLLING="AUTO">
   <FRAME NAME="MainFrame" SRC="<%= MainFramePage %>" SCROLLING="AUTO">

The <FRAMESET COLS="150,*"> line creates a frameset consisting of two columns. The width of the left-hand column is 150 pixels, and the right hand column takes up the remainder. The two <FRAME> tags provide the information about each frame. The NAME attribute defines the frame's name, and is important because it's what we use in our links and script code to refer to the frame. The SCROLLING attribute turns scrolling on if needed. But what of the SRC attribute with that familiar ASP code string <%= ... %> ?

Selecting the Pages to Display

We're telling ASP to insert whatever is in the variables NavBarPage and MainFramePage into the <FRAME> tags. The important thing is how these variables are set, and to understand this we need to look at the code we didn't show in the listing above. The page expects to find an argument in the query string, containing a parameter called Nav. In other words, it expects to be called with a URL like this:

Here's the code itself:

<%  'get Nav and use it to determine which frames to display
    'and which parameter to call navbar.asp with
NavChoice = Request.QueryString("Nav")
Select Case NavChoice
   Case "pageone"
      NavBarPage = "navbar.asp?BarChoice=pageone"
      MainFramePage = "pageone.htm"
   Case "pagetwo"
      NavBarPage = "navbar.asp?BarChoice=pagetwo"
      MainFramePage = "pagetwo.htm"
   Case "pagethree"
      NavBarPage = "navbar.asp?BarChoice=pagethree"
      MainFramePage = "pagethree.htm"
   Case "all"
      NavBarPage = "navbar.asp?BarChoice=all"
      MainFramePage = "main.htm"
   Case Else   'choose all
      Response.Redirect "frameset.asp?nav=all"
End Select  %>
... HTML code is here ...

The first line of real code uses the QueryString collection of the Request object to get the value of the Nav parameter, if it exists, from the URL. We store that value in the variable called NavChoice. With this information the code uses a Select Case statement to set the variables depending on what was specified in the URL. By using all, we can ask for the main content page (main.htm) to be displayed in the main frame, and the navigation bar to show links to all three pages.

If there is no value for Nav in the query string, as when we first loaded the page, the Else part of the Select Case construct is executed. This uses the Response object's Redirect method to refresh the page, showing the main content page and all of the links. Of course, we could have copied the code down from the all clause immediately above it—it has exactly the same effect. However, this is better style because it means we only have to change our code in one place if we decide to modify the default action.

Looking at the clauses themselves, it's easy to understand what they are doing. They put the name of the file that should be loaded into the main frame into MainFramePage. When our Nav option is pageone it's logical to think that pageone.htm should be the page we see, and so on. However, notice that setting the NavBarPage variable is a little different—we're loading the same file navbar.asp in each case. What our code does, however, is add a different parameter to the query string each time.

The Dynamic Navigation Bar

This is because the navbar.asp file changes its behavior, depending on the BarChoice value included in the URL. The part of navbar.asp that we're interested in is this:

<% BarChoice = Request.QueryString("BarChoice")
Select Case BarChoice
   Case "pageone" %>
            <TD ALIGN="CENTER"><A HREF="javascript:GoPageTwo()">
            Page Two</A></TD>
            <TD ALIGN="CENTER"><A HREF="javascript:GoPageThree()">
            Page Three</A></TD>
      <P><CENTER><H3> Page One </H3></CENTER>
... similar code for "pagetwo", "pagethree" and "all" goes here ...

This works almost identically to the code in frameset.asp. First it stores the value of the BarChoice part of the URL in a variable of the same name, and then uses this variable in a Select Case construct to output the appropriate set of <TABLE> and <A> tags. When BarChoice equals pageone, the code only outputs links to pagetwo.htm and pagethree.htm. The code for the pagetwo, pagethree, and all options is nearly identical to the code here, so we haven't listed it. You can check it out in the navbar.asp source.

Changing Both Frames with Client-Side Code: Method One

Now that we've talked about how the two most important pages are created, we can discuss that new enigma we've unearthed: the use of javascript:function-name() in the anchor tags in navbar.asp. As we said earlier, it's possible to change the contents of one frame, when a link in another is clicked, by using the TARGET attribute in normal HTML. However, in our case, what we want to do is update both of the frames in our frameset, and HTML doesn't provide a way to do this. Fortunately, our task can be accomplished with a little client-side code. We show two different methods to accomplish this in the sample, and we're ready to discuss the first now.

If you remember back to the object model discussion in the last chapter, you'll recall the Frames collection of the Document object, and the Location object. Frames provides an interface to each frame in a frameset, while the Location object gives information about the current page displayed in the frame. Each frame and window has a Location object. It's no surprise, then that two lines of code can change the contents of both of our frames:

parent.frames("NavBar").location.href = "navbar.asp?BarChoice=pageone"
parent.frames("MainFrame").location.href = "pageone.htm"

We need to remember that, to access the Location objects of the frames in our frameset, we need to go back one step to the top-level frame. The Frames collection of the NavBar and MainFrame frames are both empty because these frames don't have any sub-frames. However, the parent frame of NavBar and MainFrame includes both NavBar and MainFrame. In the code above we use the Parent property of the (default) Window object to access the correct collection. Once we have the reference to the correct frame, setting the HRef property of the Location object causes the current window to display whatever URL is specified.

The only thing we haven't talked about is how the code above actually gets executed. Normally we connect client-side code to an event raised by an object. For example, we might execute code when form button is clicked, i.e. the onClick event is fired. In this case we'd like to execute the code when a link is clicked. Link objects do have an event called onClick, and we could specify that our relocation code be executed in response to this event. We're not going to use it in this case, because then we'd have to follow the unsightly practice of specifying an HREF attribute whose value is the empty string (because our code would be doing all the work). Holding the mouse over a link with no value can confuse viewers who depend on the status bar to see where they're going. In addition, in a more graphical site we might want to do the same thing but with an image map, and these don't have onClick events like Link objects do. Instead we'll directly specify the code to executed in the HREF attribute. This is where the javascript:function-name() syntax comes into our lives.

Using javascript: function-name() with VBScript

We've been using VBScript in the last few chapters, but Internet Explorer doesn't support the vbscript:subroutine-name syntax. It does support the equivalent javascript:function-name(). The clever part if that, if we specify the name of a VBScript subroutine instead of a JavaScript function it still works. The VBScript routine is executed correctly, and this is exactly what we do in on this page of the sample. The parts of navbar.asp we haven't see yet contain these routines:

Sub GoPageOne()
   parent.frames("NavBar").location.href = "navbar.asp?BarChoice=pageone"
   parent.frames("MainFrame").location.href = "pageone.htm"
End Sub
... more similar routines here ...
<TD ALIGN="CENTER"><A HREF="javascript:GoPageOne()">Page One</A></TD>

Putting this all together, we can see that clicking the Page One link causes the browser to execute the code in the GoPageOne subroutine, and this code loads pageone.htm into the main frame and reloads navbar.asp with BarChoice equal to pageone. The rest of the Page links follow the same format. They call a VBScript function that changes the HRef properties of both frames to the correct URLs. When navbar.asp reloads, it changes the page links displayed, and everything is ready to go again.

The Option Links on Page Three

The last major feature of this example, which we haven't talked about yet, is the set of option links on page three. If you haven't already seen these, open up page three by clicking on the appropriate link.

Click on one of the option links in the main frame. The NavBar frame reloads, displaying links to all three pages and to the options that weren't selected. The page for the option we clicked is displayed in the main frame.

We could have implemented this in the same way we did the page navigation, but we've chosen an alternative method so that we can keep all of the navigation code in navbar.asp. If we used the first method, we would have to add many additional functions to the third page, making the content more difficult to modify independently of the script code, and further melding the site's logic and content—not a good thing.

In addition, suppose we had option arrays like this on each of our three pages. All of a sudden we need to maintain navigation code in four documents instead of one. Finally, as you'll see when we look at the code that implements this in navbar.asp, we are able to use the similarities between the text strings to move most of the code into a loop, further simplifying our design. In a real world situation we'd be far more likely to use entries from a database to populate this list, but even then the same code can apply—using the unique identifier for each database record instead of our arbitrary series of options from 1 to 4.

Changing Both Frames with Client-Side Code: Method Two

Those are the benefits, but how exactly does this second scheme work? Our first clue is the HREF attribute of each of the links on page three. Here's the code for the first link:

<LI><A TARGET="NavBar" HREF="navbar.asp?BarChoice=onelink&id=1">Option One</A>

We're using the TARGET attribute, which is the good old HTML-only way of targeting a page with a frame different to the one containing the link. The HTML here changes the contents of NavBar (which, since this code is in MainFrame, is the other frame) to the URL "navbar.asp?BarChoice=onelink&id=1". This code doesn't do anything about changing the page loaded into MainFrame, yet the page itself does indeed change.

It's only possible to directly change one frame or window with the TARGET attribute of the anchor tag. However, with some strategically placed client-side code, we can change more than that. In our example, code in navbar.asp is changing the contents of MainFrame, using the value of the BarChoice parameter—onelink in this case—to determine which block of code to execute. The value id parameter that follows it is used by the onelink code, as we'll see in a moment. Think about that for a second. The sequence is:

  • Click on a link in frame MainFrame that points to NavFrame and navbar.asp,

  • The navbar.asp page reloads and NavFrame is updated,

  • Code in navbar.asp reloads MainFrame.

For example, the first link on page three passes the query string BarChoice=onelink&id=1 to the navigation bar file navbar.asp. We've already looked at the pagexxx values of the BarChoice parameter that this code can handle, so let's look now at the final choice, onelink. The Select Case clauses for the other pages consisted of a block of HTML. The clause for onelink is a little more complicated, including two sets of ASP script in addition to the same HTML. We'll start with the familiar and move quickly into new territory.

The Navigation Bar 'onelink' Code

We see the same old table listing page options at the top of the finished onelink frame, and it's no surprise—the first visible HTML generated by navbar.asp for this option is the same as we've seen before in the other categories. After this table we display the list of options that aren't currently displayed. If we're currently showing Option3.htm in the main frame, we'd like to display links to options one, two, and four in the navigation bar. Our ASP code for this looks like:

<% For i = 1 to 4
      If CInt(CurrOption) <> i Then  'print link %>
         <A HREF="navbar.asp?barchoice=onelink&id=<%= i %>">
         Option <%= i %></a><p>
<%    End If
   Next %>

And it generates this HTML:

<A HREF="navbar.asp?barchoice=onelink&id=1">Option 1</a><p>
<A HREF="navbar.asp?barchoice=onelink&id=2">Option 2</a><p>
<A HREF="navbar.asp?barchoice=onelink&id=4">Option 4</a><p>

The code loops once for each element in our array of options, printing out a link every time, except for when the value stored in CurrOption is the same as our loop index. At the top of the onelink code we set a local variable called CurrOption to the value of the id portion of the query string. So calling onelink with an additional &id=3 causes CurrOption to be set to 3. This is the first use of the id parameter we've talked about—the second is in the code that loads the main frame.

Creating VBScript Code Dynamically

If you've looked at the entire code for the onelink clause you noticed this somewhat nasty looking section right at the beginning of the block:

Response.Write "<SCRIPT LANGUAGE=" & Chr(34) & "VBScript" & Chr(34) & ">" _
                & Chr(13) & Chr(10)
Response.Write "<!--" & Chr(13) & Chr(10)
Response.Write "Sub Window_OnLoad()" & Chr(13) & Chr(10)
Response.Write "On Error Resume Next" & Chr(13) & Chr(10)
strTemp = "Parent.frames(" & Chr(34) & "MainFrame" & Chr(34) _
        & ").location.href = " & Chr(34) & "Option" & CurrOption _
        & ".htm" & Chr(34)
Response.Write strTemp
Response.Write Chr(13) & Chr(10)
Response.Write "End Sub" & Chr(13) & Chr(10)
Response.Write "-->" & Chr(13) & Chr(10)
Response.Write "</SCRIPT>" & Chr(13) & Chr(10)

But, before we let ourselves be scared away by this mess, take a look at the nice and simple HTML code it generates for the browser:

Sub Window_OnLoad()
   On Error Resume Next
   Parent.frames("MainFrame").location.href = "Option3.htm"
End Sub

What we have here is a block of ASP code on the server that generates a block of code that is to be executed on the client. This is very powerful—we're actually changing our client-side code 'on the fly' to suit our purposes.

Let's first understand how navbar.asp is using this code to reload MainFrame, and then we'll talk a little more about the ASP code that generated the code in the first place. The client-side code we end up with is relatively simple. Immediately after the HTML page has finished loading, the Window_onLoad event fires. In the code that is executed, we're using the Frames collection and Location object to load the contents of Option3.htm into our main frame. The only wrinkle is the handy placement of On Error Resume Next to avoid any unsightly errors in the navigation frame if a non-existent URL is accidentally specified in the next line.

All the ASP code does is to generate this small four-line subroutine by using a series of Response.Write calls. It uses Chr(34) to generate double quotes that can't be specified directly in the code because they will be interpreted as server-side code, and the line feeds and carriage returns so the final output is neatly placed on separate lines.

Moving the Code to Our Own Site

That's all for this sample. We've covered a lot in the last few pages, and really showed how ASP can be used with client-side code to generate pages that weren't possible with just HTML or client-side code alone.

In moving the concepts in this example to your own site, you would keep much of the existing frameset.asp and navbar.asp code, modifying it to point to the actual files in your site. The rest of the files (page* and option*) would be replaced by your content.

© 1997 by Wrox Press. All rights reserved.