13.11. Multiple Windows and FramesMost of the client-side JavaScript examples we've seen so far have involved only a single window or frame. In the real world, JavaScript applications often 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 those windows. 13.11.1. Relationships Between FramesWe'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 that window itself. For frames that are direct children of a top-level window, the top property is the same as the parent property. Frames are typically created with <frameset> and <frame> tags. In HTML 4, however, as implemented in IE 4 and later and Netscape 6 and later, the <iframe> tag can also be used to create an "inline frame" within a document. As far as JavaScript is concerned, frames created with <iframe> are the same as frames created with <frameset> and <frame>. Everything discussed here applies to both kinds of frames. 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. The figure shows a browser window that contains two frames, one on top of the other. The second frame (the larger one on the bottom) itself contains three subframes, side by side. Figure 13-4. Relationships between framesWith this understanding of the relationships between frames, you may want to revisit Example 13-6, paying particular attention this time to the way the code (which is written to run in a second frame) refers to the history and location properties of the first frame. 13.11.2. Window and Frame NamesThe 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 value tells the browser where you want to display the results of activating a link, clicking on an image map, or submitting a form. For example, if you have two windows, one named table_of_contents and the other mainwin, you might have HTML like the following in the table_of_contents window: <a href="chapter01.html" target="mainwin"> Chapter 1, Introduction </a> The browser loads the specified URL when the user clicks on this hyperlink, 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 the 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.html"> 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 and later, 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 a browser 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. 13.11.3. JavaScript in Interacting WindowsRecall 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 using the frames, parent, and top properties. So, although JavaScript code in different frames is executed with different scope chains, the code in one frame can still refer to and use 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 previous example, 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. 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 defined only for a single window. Recall the Complex class we defined in Chapter 8, and consider the following multiframed HTML document: <head> <script src="Complex.js"></script> </head> <frameset rows="50%,50%"> <frame name="frame1" src="frame1.html"> <frame name="frame2" src="frame2.html"> </frameset> JavaScript code in the files frame1.html and frame2.html 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 predefined 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 then 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 the window in which the string was actually created matters. 13.11.4. Example: Colored FramesExample 13-7, a frame set that defines a grid of nine frames, demonstrates some of the techniques we've discussed 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 with the Document.write( ) method to create a new document that is empty except for a background color. 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. Example 13-7. A frame color animation<head> <title>Colored Frames</title> <script> function setcolor(w) { // Generate a random color var r = Math.floor((Math.random( ) * 256)).toString(16); var g = Math.floor((Math.random( ) * 256)).toString(16); var b = Math.floor((Math.random( ) * 256)).toString(16); var colorString = "#" + r + g + b; // Set the frame background to the random color w.document.write("<body bgcolor='" + colorString + "'></body>"); w.document.close( ); // 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> Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|