Sample Chapter 13:
Chapter 12, JavaScript in Web Browsers,
described the Window object and the central role it plays
in client-side JavaScript. We've seen that the Window
object serves as the global object for client-side JavaScript
programs, and as illustrated in Figure 12.1, it is also
the root of the client-side object hierarchy.
Besides these special roles, the Window
object is an important object in its own right. Every
web browser window and every frame within every window
is represented by a Window object. The Window object defines
quite a few properties and methods that are important
in client-side JavaScript programming. This chapter explores
those properties and methods and demonstrates some important
techniques for programming with windows and frames. Note
that because the Window object is so central to client-side
programming, this chapter is quite long. Don't feel you
have to master all this material at once - you may
find it easier to study this chapter in several shorter
chunks!
We begin this chapter with an overview
of some of the most commonly used properties and methods
of the Window object. Later sections of the chapter
explain this material in more detail. As usual, the
reference section contains complete coverage of Window
object properties and methods.
The
most important properties of the Window object are:
closed
-
A
boolean value that is true
only if the window has been closed.
defaultStatus,
status
-
These properties specify the text
that appears in the status line of the browser.
document
-
A
reference to the Document object that represents
the HTML document displayed in the window. The Document
object is covered in detail in Chapter 14, The Document
Object Model.
frames[]
-
An
array of Window objects that represent the frames
(if any) within the window.
history
-
A reference to the History object
that represents the user's browsing history for
the window.
innerHeight,
innerWidth, outerHeight,
outerWidth
-
The
inner and outer dimensions of the window; not available
in Internet Explorer 4.
location
-
A reference to the Location object
that represents the URL of the document displayed
in the window. Setting this property causes the
browser to load a new document.
locationbar,
menubar, personalbar,
scrollbars, statusbar,
toolbar
-
References
to Bar objects that specify the visibility of the
various parts of the Navigator window; not available
in IE 4.
name
-
The
name of the window. Can be used with the TARGET
attribute of the HTML <A>
tag, for example.
opener
-
A
reference to the Window object that opened this
one, or null if this
window was opened by the user.
pageXOffset,
pageYOffset
-
The
amounts that the document has been scrolled to the
right and down within the window; not available
in IE 4.
parent
-
If
the current window is a frame, a reference to the
frame of the window that contains it.
self
-
A
self-referential property; a reference to the current
Window object. A synonym for window.
top
-
If
the current window is a frame, a reference to the
Window object of the top-level window that contains
the frame. Note that top
is different from parent
for frames nested within other frames.
window
-
A
self-referential property; a reference to the current
Window object. A synonym for self.
The
Window object also supports a number of important methods:
alert(),
confirm(), prompt()
-
Display simple dialog boxes to the
user, and, for confirm()
and prompt(), get the
user's response.
close()
-
Close the window.
find(),
home(), print(),
stop()
-
Duplicate the functionality of buttons
in the Navigator button bar; these methods not available
in IE 4.
focus(),
blur()
-
Request or relinquish keyboard focus
for the window; these methods not available in IE
3.
moveBy(),
moveTo()
-
Move the window.
resizeBy(),
resizeTo()
-
Resize the window.
scrollBy(),
scrollTo()
-
Scroll the document displayed within
the window.
setInterval(),
clearInterval()
-
Schedule or cancel a function to be
repeatedly invoked with a specified delay between
invocations.
setTimeout(),
clearTimeout()
-
Schedule or cancel a function to be
invoked once after a specified number of milliseconds
-
-
-
-
-
-
-
-
As you can see from these lists, the Window
object provides quite a bit of functionality. The remainder
of this chapter explores much of that functionality
in more detail.
Three
commonly used Window methods are alert(),
confirm(), and prompt().
These methods pop up simple dialog boxes. alert()
displays a message to the user. confirm()
asks the user to click an Ok
or Cancel button to confirm
or cancel an operation. And prompt()
asks the user to enter a string. Sample dialogs produced
by these three methods are shown in Figure
13.1.
Note
that the text displayed by these dialog boxes is plain text,
not HTML-formatted text. The only formatting you can do
is with spaces, newlines, and various punctuation characters.
Adjusting the layout generally requires trial and error.
Bear in mind, though, that the dialogs look different on
different platforms and in different browsers, so you can't
always count on your formatting to look right on all possible
browsers.
Note that the word "JavaScript"
appears in the titlebar or upper left corner of all dialog
boxes produced by alert(),
confirm(), and prompt().
Although it is annoying, there is no way to get rid of it;
it is there to prevent you from writing Trojan horse code
that spoofs system dialogs and tricks users into entering
their passwords or doing other things that they shouldn't
do. (Note that Internet Explorer includes the words "Internet
Explorer" in the titlebar of its dialogs, which
is not as strong a measure to prevent Trojan horses.)
The confirm()
and prompt() methods block -
that is, those methods do not return until the user dismisses
the dialogs they display. This means that when you pop one
up, your code stops running and the currently loading document,
if any, stops loading until the user responds with the requested
input. There is no alternative to blocking for these methods -
their return value is the user's input, so they must wait
for the user before they can return. The alert()
method is also modal, except in Navigator on Unix platforms.
On these platforms, JavaScript displays the dialog and then
moves on to the next statement. In practice, this minor
incompatibility does not cause many problems.
Example
13.1 shows some typical uses of these methods.
// Here's a function that uses the alert() method to tell the user
// that form submission will take some time, and that the user should
// be patient. It would be suitable for use in the onSubmit event handler
// of an HTML form.
// Note that all formatting is done with spaces, newlines, and underscores.
function warn_on_submit()
{
alert("\n__________________________________________________\n\n" +
" Your query is being submitted...\n" +
"__________________________________________________\n\n" +
"Please be aware that complex queries such as yours\n" +
" can require a minute or more of search time.\n\n" +
" Please be patient.");
}
// Here is a use of the confirm() method to ask if the user really
// wants to visit a web page that takes a long time to download. Note that
// the return value of the method indicates the user response. Based
// on this response, we reroute the browser to an appropriate page.
var msg = "\nYou are about to experience the most\n\n" +
" -=| AWESOME |=-\n\n" +
"Web page you have ever visited!!!!!!\n\n" +
"This page takes an average of 15 minutes to\n" +
"download over a 28.8K modem connection.\n\n" +
"Are you ready for a *good* time, Dude????";
if (confirm(msg))
location.replace("awesome_page.php");
else
location.replace("lame_page.php");
// Here's some very simple code that uses the prompt() method to get
// a user's name, and then uses that name in dynamically generated HTML.
n = prompt("What is your name?", "");
document.write("<hr><h1>Welcome to my home page, " + n + "</h1><hr>");
There
is a status line at the bottom
of every browser window (except for those we explicitly
create without one). This is a location where the browser
can display messages to the user. When the user moves the
mouse over a hypertext link, for example, the browser displays
the URL that the link points to. And when the user moves
the mouse over a browser control button, the browser displays
a simple "context help"
message that explains the purpose of the button. You can
also make use of this status line in your own programs -
its contents are controlled by two properties of the Window
object: status and defaultStatus.
I've
just said that browsers display the URL of a hypertext link
when the user passes the mouse pointer over the link. This
is generally the case, but in your excursions through the
web, you may have found some links that don't behave this
way - links that display some text other than the link's
URL. This is done with the status
property of the Window object and the onMouseOver
event handler of hypertext links:
<!-- Here's how you set the status line in a hyperlink.
-- Note that the event handler *must* return true for this to work. -->
Lost? Dazed and confused? Visit the
<A HREF="sitemap.php" onMouseOver="status='Go to Site Map'; return true;">
Site Map
</A>
<!-- You can do the same thing for client-side image maps.-->
<IMG SRC="images/imgmap1.gif" USEMAP="#map1">
<MAP NAME="map1">
<AREA COORDS="0,0,50,20" HREF="info.php"
onMouseover="status='Visit our Information Center'; return true;">
<AREA COORDS="0,20,50,40" HREF="order.php"
onMouseOver="status='Place an order'; return true;">
<AREA COORDS="0,40,50,60" HREF="help.php"
onMouseOver="status='Get help fast!'; return true;">
</MAP>
The onMouseOver
event handler in the previous example must return true.
This tells the browser that it should not perform its own
default action for the event - that is, it should not
display the URL of the link in the status line. If you forget
to return true, the browser
overwrites whatever message the handler displays in the
status line with its own URL. Don't worry if you do not
fully understand the event handler in this example. We'll
explain events in Chapter 15, Events and Event Handling.
When the user moves the mouse pointer over
a hyperlink, the browser displays the URL for the link and
then erases it when the mouse moves off the hyperlink. The
same is true when you use an onMouseOver
event handler to set the Window status
property - your custom message is displayed while the
mouse is over the hyperlink, and then it is erased when
the mouse moves off the link. Or that is the way it is
supposed to work, anyway. In the Windows version of Navigator
3 (but not the Mac or X11 versions), the status line is
not automatically cleared when you set the status
property from an onMouseOver
event handler. To force it to be erased, you can use the
onMouseOut event handler, like
this:
<A HREF="sitemap.php"
onMouseOver="status='Go to Site Map'; return true;"
onMouseOut="status='';">
Site Map
</A>
The status property
is intended for exactly the sort of transient message we
saw above. Sometimes, though, you want to display a message
that is not so transient in the status line - for example,
you might display a welcome message to users visiting your
web page or a simple line of help text for novice visitors.
To do this, you set the defaultStatus
property of the Window - this property specifies the
default text displayed in the status line. That text is
temporarily replaced with URLs, context help messages, or
other transient text when the mouse pointer is over hyperlinks
or browser control buttons, but once the mouse moves off
those areas, the default text is restored.
You might use the defaultStatus
property like this to provide a friendly and helpful message
to real beginners:
<SCRIPT>
defaultStatus = "Welcome! Click on underlined blue text to navigate.";
</SCRIPT>
If your web page contains an HTML form, you might change the
defaultStatus property as the
user enters data in the form in order to display step-by-step
instructions for completing it.
The
setTimeout() method of the
Window object schedules a piece of JavaScript code to be
run at some specified time in the future. The clearTimeout()
method can be used to cancel the execution of that code.
setTimeout() is commonly used
to perform animations or other kinds of repetitive actions.
If a function runs and then uses setTimeout()
to schedule itself to be called again, we get a process
that repeats without any user intervention. JavaScript 1.2
has added the setInterval()
and clearInterval() methods,
which are like setTimeout()
and clearTimeout(), except
that they automatically reschedule the code to run repeatedly;
there is no need for the code to reschedule itself.
The setTimeout()
method is commonly used in conjunction with the status
or defaultStatus properties
to animate some kind of message in the status bar of the
browser. In general, animations involving the status bar
are gaudy and you should shun them! There are a few status
bar animation techniques, however, which can be useful and
in good taste. Example
13.2 shows such a tasteful status bar animation. It
displays the current time in the status bar and updates
that time once a minute. Because the update only occurs
once a minute, this animation does not produce a constant
flickering distraction at the bottom of the browser window
like so many others do.
Note the use of the onLoad
event handler to perform the first call to the display_time_in_status_line()
method. This event handler is invoked once when the HTML
document is fully loaded into the browser. After this first
call, the method uses setTimeout()
to schedule itself to be called every 60 seconds so that
it can update the displayed time. The onLoad
event handler is specified here as an attribute of the <BODY>
tag.
<HTML>
<HEAD>
<SCRIPT>
// This function displays the time in the status line.
// Invoke it once to activate the clock; it will call itself from then on.
function display_time_in_status_line()
{
var d = new Date(); // Get current time.
var h = d.getHours(); // Extract hours: 0 to 23.
var m = d.getMinutes(); // Extract minutes: 0 to 59.
var ampm = (h >= 12)?"PM":"AM"; // Is it am or pm?
if (h > 12) h -= 12; // Convert 24-hour format to 12-hour.
if (h == 0) h = 12; // Convert 0 o'clock to midnight.
if (m < 10) m = "0" + m; // Convert 0 minutes to 00 minutes, etc.
var t = h + ':' + m + ' ' + ampm; // Put it all together.
defaultStatus = t; // Display it in the status line.
// Arrange to do it all again in 1 minute.
setTimeout("display_time_in_status_line()", 60000); // 60000 ms is 1 minute.
}
</SCRIPT>
</HEAD>
<!-- Don't bother starting the clock till everything is loaded. The
-- status line will be busy with other messages during loading, anyway. -->
<BODY onLoad="display_time_in_status_line();">
<!-- The HTML document contents go here. -->
</BODY>
</HTML>
In JavaScript 1.2, Example
13.2 could be written using setInterval()
instead of setTimeout(). In
this case, the setTimeout()
call would be removed from the display_time_in_status_line()
method, and the onLoad event
handler would be changed to call setInterval()
to schedule an invocation of that method that automatically
repeats once every 60,000 milliseconds.
The
Window.navigator property refers
to a Navigator object that contains information about the
web browser as a whole (such as the version and the list
of data formats it can display). The Navigator object is
named after Netscape Navigator, but it is also supported
by Internet Explorer. IE also supports clientInformation
as a vendor-neutral synonym for navigator.
Unfortunately, Navigator 4 does not support this property.
The Navigator object has six main properties
that provide version information about the browser that
is running:
appName
-
The simple name of the web browser.
appVersion
-
The version number and/or other version
information for the browser.
userAgent
-
The
string that the browser sends in its USER-AGENT
HTTP header. This property typically contains all the
information in both appName
and appVersion.
appCodeName
-
The
code name of the browser. Navigator uses this property
for the code name "Mozilla," but, in general,
this property does not provide any information not provided
by other Navigator properties.
platform
-
The
hardware platform on which the browser is running. This
property is new in JavaScript 1.2.
language
-
The
language this version of the browser supports. This
is typically a standard two-letter language code, such
as "en" for English
or "fr" for French.
This property is new in Navigator 4. IE 4 defines similar
userLanguage
and systemLanguage properties
instead.
The following lines of JavaScript code display
each of these Navigator object properties in a dialog box.
Figure
13.2 shows the dialogs displayed when the code is run
on typical versions of Navigator 4 and Internet Explorer
4:
var browser = "BROWSER INFORMATION:\n";
for(var propname in navigator) {
browser += propname + ": " + navigator[propname] + "\n"
}
alert(browser);
As you can see from Figure
13.2, the properties of the Navigator object have values
that are sometimes more complex than we are interested in.
We are often only interested in the first digit of the
appVersion property, for example.
When using the Navigator object to test browser information,
we often use methods like parseInt()
and String.indexOf() to extract
only the information we want. Example
13.3 shows some code that does this: it processes the
properties of the Navigator object and stores them in an
object named browser. These
properties, in their processed form, are easier to use than
the raw navigator properties.
The general term for
code like this is a "sniffer,"
and you can find more complex and general-purpose sniffer
code on the Internet. For many purposes, however, something
as simple as that shown in Example
13.3 works just fine.
/*
* File: browser.js
* Include with: <SCRIPT SRC="browser.js"></SCRIPT>
*
* A simple "sniffer" that determines browser version and vendor.
* It creates an object named "browser" that is easier to use than
* the "navigator" object.
*/
// Create the browser object.
var browser = new Object();
// Figure out the browser major version.
browser.version = parseInt(navigator.appVersion);
// Now figure out if the browser is from one of the two
// major browser vendors. Start by assuming it is not.
browser.isNavigator = false;
browser.isIE = false;
if (navigator.appName.indexOf("Netscape") != -1)
browser.isNavigator = true;
else if (navigator.appName.indexOf("Microsoft") != -1)
browser.isIE = true;
As you probably noticed, Figure
13.2 shows that the Navigator object has properties
other than those we have discussed so far. Many of these
properties are incompatible extensions in Internet Explorer
and are not discussed here. If you want to make use of
the properties, be sure that you first use code like that
in Example
13.3 to ensure that
your code is running in Internet Explorer 4 or later.
Two properties, mimeTypes
and plugins, do bear further
discussion, however. These properties are supported in
Navigator 3 and later. In IE 4, the properties are defined
for compatibility but are not truly supported: the arrays
they refer to are always empty.
The
navigator.mimeTypes[] property
is an array of MimeType objects, each of which describes
one MIME data format (text/html
and image/gif, for example)
that the web browser can display (either directly, with
an external helper application, or with a plugin). The
MimeType object itself contains properties that describe
the data format.
The mimeTypes[]
array is indexed numerically, but it is also an associative
array, indexed by the name of the MIME type. Thus, you
can easily check for support of a given data format on
the browser:
// Check to see if the browser can display MPEG files.
var show_movie = (navigator.mimeTypes["video/mpeg"] != null);
The
navigator.plugins[] property
is an array of Plugin objects, each of which represents
one plugin module that has been installed in the browser.
The properties of the Plugin object provide various details
about the plugin. The Plugin object also contains array
elements, which are MimeType objects describing each of
the data formats supported by that particular plugin.
Note that this array is different than the navigator.mimeTypes[]
array described earlier.
You can use the plugins[]
property as an associative array, just as you can the
mimeTypes[] property. This
lets you check for the existence of a particular plugin
without having to loop through the array numerically and
check every element:
// Check to see if the browser has the Shockwave plugin installed.
var shocked = (navigator.plugins["Shockwave"] != null);
In
addition to its properties, the Navigator object defines
methods that provide further information about the browser.
In JavaScript 1.1 and later, the javaEnabled()
method returns true if the
browser supports Java and if Java support is enabled;
otherwise it returns false.
Navigator 4 adds the preference()
method, which allows signed scripts to query and set user
preferences.
In
JavaScript 1.2, the screen
property of a Window object refers to a Screen object. This
Screen object provides information about the size of the
user's display and the number of colors available. The width
and
height properties specify the
size of the display in pixels. The availWidth
and availHeight
properties specify the display size that is actually available:
they exclude the space required by features like the Windows
95 taskbar. You can use these properties to help you decide
what size images to include in a document, for example,
or in a program that creates multiple browser windows, what
size windows to create.
The
colorDepth property specifies
the base-2 logarithm of the number of colors that can be
displayed. Often, this value is the same as the number of
bits per pixel used by the display. For example, an 8-bit
display can display 256 colors, and if all of these colors
were available for use by the browser, the screen.colorDepth
property would be 8. In some circumstances, however, the
browser may restrict itself to a subset of the available
colors, and you might find a screen.colorDepth
value that is lower than the bits-per-pixel value of the
screen. If you have several versions of an image that were
defined using different numbers of colors, you can test
this colorDepth property to
decide which version to include in a document.
The
Window object defines several methods that allow high-level
control of the window itself. The following sections explore
how these methods allow us to open and close windows, to
control window position and size, to request and relinquish
keyboard focus, and to scroll the contents of a window.
We conclude with an example that demonstrates several of
these features.
You
can open a new web browser window with the open()
method of the Window object. This method takes four optional
arguments and returns a Window object that represents
the newly opened window. The first argument to open()
is the URL of the document to display in the new window.
If this argument is omitted (or is null
or the empty string), the window will be empty.
The second argument to open()
is the name of the window. As we'll discuss later in the
chapter, this name can be useful as the value of the TARGET
attribute of a <FORM>
or <A> tag. If you
specify the name of a window that already exists, open()
simply uses that existing window rather than opening a
new one.
The third optional argument to open()
is a list of features that specify the window size and
GUI decorations. If you omit this argument, the new window
is given a default size and has a full set of standard
features: a menubar, status line, toolbar, and so on.
On the other hand, if you specify this argument, you can
explicitly specify the size of the window and the set
of features it includes. For example, to open a small
resizeable browser window with a status bar but no menubar,
toolbar, or locationbar, you could use the following line
of JavaScript:
var w = window.open("smallwin.php", "smallwin",
"width=400,height=350,status=yes,resizeable=yes");
Note that when you specify this third argument, any features
you do not explicitly specify are omitted. The reference
section documents the full set of available features and
their names.
The fourth argument to open()
is useful only when the second argument names an already
existing window. This fourth argument is a boolean value
that specifies whether the URL specified as the first
argument should replace the current entry in the window's
browsing history (true) or
create a new entry in the window's browsing history (false),
which is the default behavior.
The return value of the open()
method is the Window object that represents the newly
created window. You can use this Window object in your
JavaScript code to refer to the new window, just as you
use the implicit Window object window
to refer to the window within which your code is running.
But what about the reverse situation? What if JavaScript
code in the new window wants to refer back to the window
that opened it? In JavaScript 1.1 and later, the
opener property of a window
refers to the window from which it was opened. If the
window was created by the user instead of by JavaScript
code, the opener property
is null.
An important point about the open()
method is that it is almost always invoked as window.open(),
even though window refers
to the global object and should therefore be entirely
optional. The reason that window
is specified explicitly is that the Document object also
has an open() method, so
specifying window.open()
helps to make it very clear what we are trying to do.
This is not only a helpful habit; it is required in some
circumstances, because, as we'll learn in Chapter 15,
event handlers execute in the scope of the object that
defines them. When the event handler of an HTML button
executes, for example, the scope chain includes the Button
object, the Form object that contains the button, the
Document object that contains the form, and, finally,
the Window object that contains the document. Thus, if
such an event handler refers merely to the open()
method, this identifier ends up being resolved in the
Document object, and the event handler opens a new document
rather than opening a new window!
We'll see an example of the open()
method in Example
13.4.
Just
as the open() method opens
a new window, the close()
method closes one. If we've created a Window object w,
we can close it with:
w.close();
JavaScript code running within that window itself could
close it with:
window.close();
Again, note the explicit use of the
window
identifier to disambiguate the
close()
method of the Window object from the
close()
method of the Document object.
You can only automatically close windows
that your own JavaScript code has created. If you attempt
to close any other window, the user is presented with
a dialog box that asks him to confirm (or cancel) that
request to close the window. This prevents inconsiderate
web sites from closing your main browsing window.
In JavaScript 1.1 and later, a Window object
continues to exist after the window it represents has
been closed. You should not attempt to use any of its
properties or methods, however, except
to test the closed property.
This property is true if
the window has been closed. Remember that the user can
close any window at any time, so to avoid errors, it is
a good idea to check periodically that the window you
are trying to use is still open. We'll see an example
of doing just this in Example
13.4.
In
JavaScript 1.2, moveTo()
moves the upper-left corner of the window to the specified
coordinates. Similarly, moveBy()
moves the window a specified number of pixels left or
right and up or down. resizeTo()
and resizeBy() resize the
window by an absolute or relative amount; they are also
new in JavaScript 1.2. Note that in Navigator 4,
there are some security restrictions on how you can move
and resize windows. We'll see more about this in Chapter
21, JavaScript Security, but basically, you are not allowed
to move a window off screen or to make it too small. This
is so that you can't create a hidden window that the user
might forget about.
The
focus() and blur()
methods also provide high-level control over a window.
Calling focus() requests
that the system give keyboard focus to the window and
blur() relinquishes keyboard
focus. These methods are defined in JavaScript 1.1 and
later.
Window
also contains some methods that control the scrollbars
of the window, rather than the window itself. scrollBy()
scrolls the document displayed in the window by a specified
number of pixels left or right and up or down. scrollTo()
scrolls the document to an absolute position. It moves
the document so that the specified document coordinates
are displayed in the upper-left corner of the document
area within the window. These two methods are
defined in JavaScript 1.2. In JavaScript 1.1, the scroll()
method performs the same function as the JavaScript 1.2
scrollTo() method. scrollTo()
is the preferred method, but scroll()
remains for backwards compatibility.
In
JavaScript 1.2, the elements of the anchors[]
array of the Document object are Anchor objects. Each
Anchor object has x and y
properties that specify the location of the anchor within
the document. Thus, you can use these values in conjunction
with the scrollTo() method
to scroll to known locations within the document.
Example
13.4 demonstrates the Window open(),
close(), and moveTo()
methods and several other window programming techniques
that we've discussed. It creates a new window and then
uses setInterval() to repeatedly
call a function that moves it around the screen. It determines
the size of the screen with the Screen object and then
uses this information to make the window "bounce"
when it reaches any edge of the screen.
<script>
// Here are the initial values for our animation.
var x = 0, y = 0, w=200, h=200; // Window position and size
var dx = 5, dy = 5; // Window velocity
var interval = 100; // Milliseconds between updates
// Create the window that we're going to move around.
// The javascript: URL is simply a way to display a short document.
// The final argument specifies the window size.
var win = window.open('javascript:"<H1>BOUNCE!</H1>"', "",
"width=" + w + ",height=" + h);
// Set the initial position of the window.
win.moveTo(x,y);
// Use setInterval() to call the bounce() method every interval
// milliseconds. Store the return value so that we can stop the
// animation by passing it to clearInterval().
var intervalID = window.setInterval("bounce()", interval);
// This function moves the window by (dx, dy) every interval ms.
// It bounces whenever the window reaches the edge of the screen.
function bounce() {
// If the user closed the window, stop the animation.
if (win.closed) {
clearInterval(intervalID);
return;
}
// Have we reached the right or left edge?
if ((x+dx > (screen.availWidth - w)) || (x+dx < 0)) dx = -dx;
// Have we reached the bottom or top edge?
if ((y+dy > (screen.availHeight - h)) || (y+dy < 0)) dy = -dy;
// Update the current position of the window.
x += dx;
y += dy;
// Finally, move the window to the new position.
win.moveTo(x,y);
}
</script>
<!-- Clicking this button stops the animation! -->
<FORM>
<INPUT TYPE=button VALUE="Stop"
onClick="clearInterval(intervalID); win.close();">
</FORM>
The
location property of a window
is a reference to a Location object - a representation
of the URL of the document currently being displayed in
that window. The
href property of the Location
object is a string that contains the complete text of the
URL. Other
properties of this object, such as protocol,
host, pathname,
and search, specify the various
individual parts of the URL.
This
search property of the Location
object is an interesting one. It contains any portion of
a URL following (and including) a question mark. This is
often some sort of query string. In general, the question
mark syntax in a URL is a technique for embedding arguments
in the URL. While these arguments are usually intended for
CGI scripts run on a server, there is no reason why they
cannot also be used in JavaScript-enabled pages. Example
13.5 shows the definition of a general-purpose getArgs()
function that you can use to extract arguments from the
search property of a URL. It
also shows how this getArgs()
method could have been used to set initial values of the
bouncing window animation parameters in Example
13.4.
/*
* This function parses comma-separated name=value argument pairs from
* the query string of the URL. It stores the name=value pairs in
* properties of an object and returns that object.
*/
function getArgs() {
var args = new Object();
var query = location.search.substring(1); // Get query string.
var pairs = query.split(","); // Break at comma.
for(var i = 0; i < pairs.length; i++) {
var pos = pairs[i].indexOf('='); // Look for "name=value".
if (pos == -1) continue; // If not found, skip.
var argname = pairs[i].substring(0,pos); // Extract the name.
var value = pairs[i].substring(pos+1); // Extract the value.
args[argname] = unescape(value); // Store as a property.
}
return args; // Return the object.
}
/*
* We could have used getArgs() in the previous bouncing window example
* to parse optional animation parameters from the URL.
*/
var args = getArgs(); // Get arguments.
if (args.x) x = parseInt(args.x); // If arguments are defined...
if (args.y) y = parseInt(args.y); // ... override default values.
if (args.w) w = parseInt(args.w);
if (args.h) h = parseInt(args.h);
if (args.dx) dx = parseInt(args.dx);
if (args.dy) dy = parseInt(args.dy);
if (args.interval) interval = parseInt(args.interval);
In addition to its properties, the Location
object can be used as if it were itself a primitive string
value. If you read the value of a Location object, you get
the same string as you would if you read the href
property of the object (because the Location object has
a suitable toString() method).
What is far more interesting, though, is that you can assign
a new URL string to
the location property of a
window. Assigning a URL to the Location object like this
has a very important side effect: it causes the browser
to load and display the contents of the URL you assign.
For example, you might assign a URL to the location
property like this:
// If Java isn't enabled, go to a page that displays a message
// saying that you can't run this page without Java.
if (!navigator.javaEnabled())
location = "needsjava.php";
As
you can imagine, making the browser load specified web pages
into windows is a very important programming technique.
While you might expect there to be a method you can call
to make the browser display a new web page, assigning a
URL to the location property
of a window is the supported technique to accomplish this.
Although the Location object does not have
a method that serves the same function as assigning a URL
directly to the location property
of a window, this object does support two methods (added
in JavaScript 1.1). The
reload() method reloads the
currently displayed page from the web server. The replace()
method loads and displays a URL that you specify. But invoking
this method for a given URL is different than assigning
that URL to the location property
of a window. When you call replace(),
the specified URL replaces the current one in the browser's
history list rather than creating a new entry in that history
list. Therefore, if you use replace()
to overwrite one document with a new one, the Back
button does not take the user back to the original document,
as it does if you load the new document by assigning to
the location property. For
web sites that use frames and display a lot of temporary
pages (perhaps generated by a CGI script), using replace()
is often useful. By not storing temporary pages in the history
list, the Back button becomes
more useful to the user.
Finally,
don't confuse the location
property of the Window object, which refers to a Location
object, with the location property
of the Document object, which is simply a read-only string
with none of the special features of the Location object.
Document.location is a synonym
for Document.URL, which in
Navigator 3 is the preferred name for this property (because
it avoids the potential confusion). In most cases, document.location
is the same as location.href.
When there is a server redirect, however, document.location
contains the actual URL as loaded, and location.href
contains the URL as originally requested.
The
history property of the Window
object refers to a History object for the window. The History
object is an array of the URLs in the browsing history of
the window or frame. For a top-level Navigator window, the
History object is a representation of the contents of the
browser's Go menu.
A
user's browsing session history is private information,
so, for security reasons, there are heavy restrictions on
how the History
object can be used. In Navigator 4, the elements of the
history array are accessible
to signed scripts. In other versions of Navigator and in
Internet Explorer, the elements of the array are never accessible,
and the History object is less useful.
The
History object supports three methods, which can be used
by unsigned scripts in all versions of Navigator and Internet
Explorer. The back() and forward()
methods perform the same action as clicking on the Back
and Forward browser buttons.
The third method,
go(), suffers from bugs in
Navigator 2 and 3 and has incompatible behavior in Internet
Explorer 3; it is best avoided.
Example
13.6 shows how you might use the back()
and forward() methods of the
History object and the Location object to add a navigation
bar to a framed web site. Figure
13.3 shows what a navigation bar looks like. Note that
the example uses JavaScript with multiple frames, which
is something we will discuss shortly. It also contains a
simple HTML form and uses JavaScript to read and write values
from the form. This is covered in detail in Chapter 16,
Forms and Form Elements.
<!-- This file implements a navigation bar, designed to go in a frame at
the bottom of a window. Include it in a frameset like the following:
<frameset rows="*,75">
<frame src="about:blank">
<frame src="navigation.php">
</frameset>
-->
<SCRIPT>
// The function is invoked by the Back button in our navigation bar.
function go_back()
{
// First, clear the URL entry field in our form.
document.navbar.url.value = "";
// Then use the History object of the main frame to go back.
parent.frames[0].history.back();
// Wait a second, and then update the URL entry field in the form
// from the location.href property of the main frame. The wait seems
// to be necessary to allow the location.href property to get in sync.
setTimeout("document.navbar.url.value = parent.frames[0].location.href;",
1000);
}
// This function is invoked by the Forward button in the navigation bar.
// It works just like the one above.
function go_forward()
{
document.navbar.url.value = "";
parent.frames[0].history.forward();
setTimeout("document.navbar.url.value = parent.frames[0].location.href;",
1000);
}
// This function is invoked by the Go button in the navigation bar, and also
// when the form is submitted (when the user hits the Return key).
function go_to()
{
// Just set the location property of the main frame to the URL
// that the user typed in.
parent.frames[0].location = document.navbar.url.value;
}
</SCRIPT>
<!-- Here's the form, with event handlers that invoke the functions above. -->
<FORM NAME="navbar" onSubmit="go_to(); return false">
<INPUT TYPE="button" VALUE="Back" onClick="go_back();">
<INPUT TYPE="button" VALUE="Forward" onClick="go_forward()">
URL:
<INPUT TYPE="text" NAME="url" SIZE=50">
<INPUT TYPE="button" VALUE="Go" onClick="go_to()">
</FORM>
Most
of the client-side JavaScript examples we've seen so far
have involved only a single window or frame. In the real
world, most interesting JavaScript applications involve
multiple windows or frames. Recall that frames within a
window are represented by Window objects; JavaScript makes
little distinction between windows and frames. In the most
interesting applications, there is JavaScript code that
runs independently in each of several windows. The next
section explains how the JavaScript code in each window
can interact and cooperate with each of the other windows
and with the scripts running in each of these windows.
We've
already seen that the open()
method of the Window object returns a new Window object
representing the newly created window. We've also seen
that this new window has an opener
property that refers back to the original window. In this
way, the two windows can refer to each other, and each
can read properties and invoke methods of the other. The
same thing is possible with frames. Any frame in a window
can refer to any other frame through the use of the frames[],
parent, and top
properties of the Window object.
Every
window has a frames property.
This property refers to an array of Window objects, each
of which represents a frame contained within the window.
(If a window does not have any frames, the frames[]
array is empty and frames.length
is zero.) Thus, a window (or frame) can refer to its first
subframe as frames[0], its
second subframe as frames[1],
and so on. Similarly, JavaScript code running in a window
can refer to the third subframe of its second frame like
this:
frames[1].frames[2]
Every
window also has a parent
property, which refers to the Window object in which it
is contained. Thus, the first frame within a window might
refer to its sibling frame (the second frame within the
window) like this:
parent.frames[1]
If a window is a top-level window and not a frame,
parent
simply refers to the window itself:
parent == self; // For any top-level window
If a frame is contained within another frame
that is contained within a top-level window, that frame
can refer to the top-level window as parent.parent.
The top
property is a general-case shortcut, however: no matter
how deeply a frame is nested, its top
property refers to the top-level containing window. If
a Window object represents a top-level window, top
simply refers to the window itself. For frames that are
direct children of a top-level window, the top
property is the same as the parent
property.
Figure
13.4 illustrates these relationships between frames
and shows how code running in any one frame can refer
to any other frame through the use of the frames,
parent, and top
properties. With this understanding of the relationships
between windows, you may want to revisit Example
13.6, paying particular attention this time to the
way the second frame refers to the history
and location properties of
the first.
The
second, optional argument to the open()
method discussed earlier is a name for the newly created
window. When you create a frame with the HTML <FRAME>
tag, you can specify a name
with the NAME attribute.
An important reason to specify names for windows and frames
is that those names can be used as the value of the TARGET
attribute
of the <A>, <MAP>,
and <FORM> tags. This
tells the browser where you want the results of activating
a link, clicking on an image map, or submitting a form
to be displayed.
For example, if you have two windows, one
named table_of_contents and
the other named mainwin,
you might have HTML like the following in the table_of_contents
window:
<A HREF="chapter01.php" TARGET="mainwin">
Chapter 1, Introduction
</A>
When the user clicks on this hyperlink, the browser loads
the specified URL, but instead of displaying the URL in
the same window as the link, it displays it in the window
named
mainwin. If there is
no window with the name
mainwin,
clicking the link creates a new window with that name and
loads the specified URL into it.
The TARGET
and NAME attributes are part
of HTML and operate without the intervention of JavaScript,
but there are also JavaScript-related reasons to give
names to your frames. We've seen that every Window object
has a frames[] array that
contains references to each of its frames. This array
contains all frames in a window (or frame) whether or
not they have names. If a frame is given a name, however,
a reference to that frame is also stored in a new property
of the parent Window object. The name of that new property
is the same as the name of the frame. Therefore, you might
create a frame with HTML like this:
<FRAME NAME="table_of_contents" SRC="toc.php">
Now you can refer to that frame from another, sibling frame
with:
parent.table_of_contents
This makes your code easier to read and understand than
using (and relying on) a hardcoded array index, as you'd
have to do with an unnamed frame:
parent.frames[1]
The
name property of any Window
object
contains the name of that window. In JavaScript 1.0, this
property is read-only. In JavaScript 1.1, however, you
can set this property, thereby changing the name of a
window or a frame. One common reason to do this is to
set the name of the initial browser window. When Navigator
starts up, the initial window has no name, so it cannot
be used with the TARGET attribute.
If you set the name property
of the window, however, you can then use that name in
TARGET attributes.
Recall
what we learned in Chapter 12: the Window object serves
as the global object for client-side JavaScript code,
and the window serves as the execution context for all
JavaScript code it contains. This holds true for frames
as well: every frame is an independent JavaScript execution
context. Because every Window object is its own global
object, each window defines its own namespace and its
own set of "global"
variables. When viewed from the perspective of multiple
frames or windows, global variables do not seem all that
global, after all!
Although each window and frame defines an
independent JavaScript execution context, this does not
mean that JavaScript code running in one window is isolated
from code running in other windows. Code running in one
frame has a different Window object at the top of its
scope chain than code running in another frame. However,
the code from both frames is executed by the same JavaScript
interpreter, in the same JavaScript environment. As we've
seen, one frame can refer to any other frame by using
the frames, parent,
and top properties. So, although
JavaScript code in different frames is executed with different
scope chains, this does not prevent the code in one frame
from referring to, and using, the variables and functions
defined by code in another frame.
For example, suppose code in frame A defines
a variable i:
var i = 3;
That variable is nothing more than a property of the global
object - a property of the Window object. Code in frame
A could refer to the variable explicitly as such a property
with either of these two expressions:
window.i
self.i
Now suppose that frame A has a sibling frame B that wants
to set the value of the variable
i
defined by the code in frame A. If frame B just sets a variable
i, it merely succeeds in creating
a new property of its own Window object. So instead, it
must explicitly refer to the property
i
in its sibling frame with code like this:
parent.frames[0].i = 4;
Recall
that the function keyword
that defines functions declares a variable just like the
var keyword does. If JavaScript
code in frame A declares a function f,
that function is defined only within frame A. Code in
frame A can invoke f like this:
f();
Code in frame B, however, must refer to
f
as a property of the Window object of frame A:
parent.frames[0].f();
If the code in frame B needs to use this function frequently,
it might assign the function to a variable of frame B so
that it can more conveniently refer to the function:
var f = parent.frames[0].f;
Now code in frame B can invoke the function as
f(),
just as code in frame A does.
When you share functions between frames
or windows like this, it is very important to keep the
rules of lexical scoping in mind. A function is executed
in the scope in which it was defined, not in the scope
from which it is invoked. Thus, to continue with the example
above, if the function f
refers to global variables, these variables are looked
up as properties of frame A, even when the function is
invoked from frame B.[1]
If you don't pay careful attention to this,
you can end up with programs that behave in unexpected
and confusing ways. For example, suppose you define the
following function in the <HEAD>
section of a multiframe document, with the idea that it
will help with debugging:
function debug(msg) {
alert("Debugging message from frame: " + name + "\n" + msg);
}
The JavaScript code in each of your frames can refer to
this function as
top.debug().
Whenever this function is invoked, however, it looks up
the variable
name in the context
of the top-level window in which the function is defined,
rather than the context of the frame from which it is invoked.
Thus, the debugging messages always carry the name of the
top-level window, rather than the name of the frame that
sent the message, as was intended.
Remember
that constructors are also functions, so when you define
a class of objects with a constructor function and an
associated prototype object, that class is only defined
for a single window. Recall the Complex class we defined
in Chapter 8, Objects, and consider the following multiframed
HTML document:
<HEAD>
<SCRIPT SRC="Complex.js"></SCRIPT>
</HEAD>
<FRAMESET ROWS="50%,50%">
<FRAME NAME="frame1" SRC="frame1.php">
<FRAME NAME="frame2" SRC="frame2.php">
</FRAMESET>
JavaScript code in the files
frame1.php
and
frame2.php cannot create
a Complex object with an expression like this:
var c = new Complex(1,2); // Won't work from either frame
Instead, code in these files must explicitly refer to the
constructor function:
var c = new top.Complex(3,4);
Alternatively, code in either frame can define its own variable
to refer more conveniently to the constructor function:
var Complex = top.Complex;
var c = new Complex(1,2);
Unlike user-defined constructors, predefined
constructors are automatically pre-defined in all windows.
Note, however, that each window has an independent copy
of the constructor and an independent copy of the constructor's
prototype object. For example, each window has its own
copy of the String() constructor
and the String.prototype
object. So, if you write a new method for manipulating
JavaScript strings and make it a method of the String
class by assigning it to the String.prototype
object in the current window, all strings in that window
can use the new method. However, the new method is not
accessible to strings defined in other windows. Note that
it does not matter which window holds a reference to the
string, only which window the string was actually created
in.
Example
13.7, a frame set that defines a grid of nine frames,
demonstrates some of the techniques we've been discussing
in this chapter. The <HEAD>
section of the frame set includes a <SCRIPT>
that defines a JavaScript function named setcolor().
The onLoad event handler
of the <FRAMESET> tag
invokes setcolor() once for
each of the nine frames.
setcolor()
is passed a Window object as its argument. It generates
a random color and uses it as the new value of the bgColor
property of the Document object. (We'll see more about
the Document object and its properties in Chapter 14.)
Finally, setcolor() uses
the setTimeout() method to
schedule itself to be called again in one second. This
call to setTimeout() is the
most interesting part of the example. Notice especially
how it uses the parent and
name properties of Window
objects.
<HEAD>
<TITLE>Colored Frames</TITLE>
<SCRIPT>
function setcolor(w) {
// Generate a random color.
var r = (Math.random() * 256).toString(16);
var g = (Math.random() * 256).toString(16);
var b = (Math.random() * 256).toString(16);
var colorString = "#" + r + g + b;
// Set the frame background to the random color.
w.document.bgColor = colorString;
// Schedule another call to this method in one second.
// Since we call the setTimeout() method of the frame, the string
// will be executed in that context, so we must prefix properties
// of the top-level window with "parent."
w.setTimeout('parent.setcolor(parent.' + w.name + ')', 1000);
// We could also have done the same thing more simply like this.
// setTimeout('setcolor(' + w.name + ')', 1000);
}
</SCRIPT>
</HEAD>
<FRAMESET rows="33%,33%,34%" cols="33%,33%,34%"
onLoad="for(var i = 0; i < 9; i++) setcolor(frames[i]);">
<FRAME NAME="f1" SRC="javascript:''"><FRAME NAME="f2" SRC="javascript:''">
<FRAME NAME="f3" SRC="javascript:''"><FRAME NAME="f4" SRC="javascript:''">
<FRAME NAME="f5" SRC="javascript:''"><FRAME NAME="f6" SRC="javascript:''">
<FRAME NAME="f7" SRC="javascript:''"><FRAME NAME="f8" SRC="javascript:''">
<FRAME NAME="f9" SRC="javascript:''">
</FRAMESET>