Article

Script Smarter: Quality JavaScript from Scratch

Page: 1 2 3 4 5 6 7 8 Next

Communicating Between Frames

If you're working in a framed environment, it may be necessary to have scripts communicate between frames, either reading or writing properties, or calling functions in different documents.

If you have a choice about whether or not to use frames, I'd strongly advise against doing so, because they have many serious usability and accessibility problems, quite apart from the fact that they're conceptually broken (they create within the browser states that cannot be addressed). But as with your use of popups, in some cases you may not have a choice about your use of frames. So if you really must use them, here's what you'll need to do.

Solution

Let's begin with a simple frameset document:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"      
   "http://www.w3.org/TR/html4/frameset.dtd">      
<html>      
 <head>      
   <title>A frameset document</title>      
 </head>      
 <frameset cols="200, *">      
   <frame src="navigation.html" name="navigationFrame">      
   <frame src="content.html" name="contentFrame">      
   <noframes>      
     <p>This frameset document contains:</p>      
     <ul>      
       <li><a href="navigation.html">Site navigation</a></li>      
       <li><a href="contents.html">Main content</a></li>      
     </ul>      
   </noframes>      
 </frameset>      
</html>

We can use four references for cross-frame scripting:

  • window or self refers to the current framed page.
  • parent refers to the page that contains the frame that contains the current page.
  • top refers to the page at the very top of the hierarchy of frames, which will be the same as parent if there's only one frameset in the hierarchy.
  • The frames collection is an associative array of all the frames in the current page.

Let's say we have a script in contentFrame that wants to communicate the page in navigationFrame. Both pages are contained in a single frameset -- the only one in the hierarchy -- so we could successfully make any of the following references from within contentFrame:

  • parent.frames[0]
  • top.frames[0]
  • parent.frames['navigationFrame']
  • top.frames['navigationFrame']

The frames collection is an associative array (like the forms collection we saw in Chapter 6, Processing and Validating Forms), so each element can be accessed by either index or name. It's generally best to use the name (unless you have a good reason not to) so that you won't have to edit your code later if the frame order changes. By the same token, parent references in a complex nested frameset can change if the hierarchy changes, so I generally recommend that developers always start referencing from top. Of the above options, the reference I prefer, then, is top.frames['navigationFrame'].

Now that we have a reference to the frame, we can call a function in the other framed page:

Example 7.6. frames-navigation.js (excerpt)      
     
var navframe = top.frames['navigationFrame'];      
navframe.callMyFunction();

Alternatively, we can get a reference to the other framed document, and work with the DOM from there:

Example 7.7. frames-navigation.js (excerpt)      
     
var navdoc = navframe.document;      
var menu = navdoc.getElementById('menulist');

Discussion

Communication between frames is only allowed for documents in the same domain -- for security reasons, it's not possible to work with a document that was loaded from a different domain than the script. It wouldn't do, for example, for a malicious site owner to load a site that you visit regularly into a frame, and steal the personal data you enter there.

In fact, some browsers let users disallow all scripts from communicating between frames, just to eradicate any possibility of a cross-site scripting vulnerability, and there's no way to work around this preference if your script finds itself running in a browser so configured.

If you do have users who are complaining of problems (and they can't or won't change their settings to allow cross-frame scripting), the safest thing to do is simply to avoid cross-frame scripting altogether.

Alternative methods of passing data between pages are discussed in Chapter 6, Processing and Validating Forms and Chapter 8, Working with Cookies.

Getting the Scrolling Position

Page scrolling is one of the least-standardized properties in JavaScript: three variations are now in use by different versions of different browsers. But with a few careful object tests, we can reliably get a consistent value.

Solution

There are three ways of getting this information. We'll use object tests on each approach, to determine the level of support available:

Example 7.8. get-scrolling-position.js (excerpt)      
     
function getScrollingPosition()      
{      
 var position = [0, 0];      
     
 if (typeof window.pageYOffset != 'undefined')      
 {      
   position = [      
       window.pageXOffset,      
       window.pageYOffset      
   ];      
 }      
     
 else if (typeof document.documentElement.scrollTop      
     != 'undefined' && document.documentElement.scrollTop > 0)      
 {      
   position = [      
       document.documentElement.scrollLeft,      
       document.documentElement.scrollTop      
   ];      
 }      
     
 else if (typeof document.body.scrollTop != 'undefined')      
 {      
   position = [      
       document.body.scrollLeft,      
       document.body.scrollTop      
   ];      
 }      
     
 return position;      
}

The function can now be called as required. Here's a simple demonstration, using a window.onscroll event handler, that gets the figures and writes them to the title bar:

Example 7.9. get-scrolling-position.js (excerpt)      
     
window.onscroll = function()      
{      
 var scrollpos = getScrollingPosition();      
 document.title = 'left=' + scrollpos[0] + ' top=' +      
     scrollpos[1];      
};

The Problem with scroll

scroll is not the most reliable of events: it may not fire at all in Konqueror or Safari 1.0, or when the user navigates with a mouse wheel in Firefox. And if it does fire, it may do so continually and rapidly (as it does in Internet Explorer), which can be slow and inefficient if the scripting you set to respond to the event is very complex.

If you have difficulties of this kind, you may find it better to use the setInterval function instead of an onscroll event handler. setInterval will allow you to call the function at a predictable interval, rather than in response to an event. You can find out more about this kind of scripting in Chapter 14, Time and Motion, but here's a comparable example:

window.setInterval(function()      
{      
 var scrollpos = getScrollingPosition();      
 document.title = 'left=' + scrollpos[0] + ' top=' +      
     scrollpos[1];      
}, 250);

Discussion

The only real complication here is that IE 5 actually does recognize the documentElement.scrollTop property, but its value is always zero, so we have to check the value as well as looking for the existence of the property.

Otherwise, it doesn't really matter to us which browser is using which property; all that matters is that our script gets through one of the compatibility tests and returns a useful value. However, the properties used by each browser are shown here for reference:

  • window.pageYOffset is used by Firefox and other Mozilla browsers, Safari, Konqueror, and Opera.
  • document.documentElement.scrollTop is used by IE 6 in standards-compliant mode.
  • document.body.scrollTop is used by IE 5, and IE 6 in "Quirks" mode.

This list doesn't tell the complete story, but it's intended primarily to describe the ordering of the tests. More recent Mozilla browsers (such as Firefox) also support documentElement.scrollTop and body.scrollTop, by the same rendering mode rules as IE 6. Safari and Konqueror support body.scrollTop in either mode. Opera supports all three properties in any mode!

But none of this is important for you to know -- browser vendors add these multiple properties to allow for scripts that are unaware of one property or another, not to provide arbitrary choices for the sake of it. From our perspective, the important point is to settle on a set of compatibility tests that ensures our script will work as widely as possible.

Rendering Modes

"Standards" mode and "Quirks" mode are the two main rendering modes in use by current browsers. These modes affect various aspects of the output document, including which element is the canvas (<body> or <html>), and how CSS box sizes are calculated. For more on rendering modes, see Chapter 11, Detecting Browser Differences.

Making the Page Scroll to a Particular Position

All current browsers implement the same (nonstandard) methods for scrolling a page. At least something here is simple!

Solution

There are two methods that can be used to scroll the page (or rather, the window or frame), either by a particular amount (window.scrollBy), or to a particular point (window.scrollTo):

Example 7.10. scroll-page.js (excerpt)      
     
//scroll down 200 pixels      
window.scrollBy(0, 200);      
     
Example 7.11. scroll-page.js (excerpt)      
     
//scroll across 200 pixels      
window.scrollBy(200, 0);      
     
Example 7.12. scroll-page.js (excerpt)      
     
//scroll to 300 from the edge and 100 from the top      
window.scrollTo(300, 100);      
     
Example 7.13. scroll-page.js (excerpt)      
     
//scroll to the beginning      
window.scrollTo(0, 0);

These examples say: scroll down by 200 pixels, then across by 200 pixels, then to a point that's 300 pixels from the left and 100 pixels from the top, then back to the top corner.

Getting the Viewport Size (the Available Space inside the Window)

The details of the viewport size are needed for many kinds of scripting, wherever available space is a factor in the script's logic. This solution provides a utility function for getting the viewport size We'll be seeing the function again quite a few times throughout this book!

Solution

The properties we need are implemented in three different ways, like the properties we saw for page scrolling in the previous section (the section called "Making the Page Scroll to a Particular Position"). As was the case in that example, we can use object testing to determine which implementation is relevant, including the test for a zero-value that we need in IE 5 (this test is required for the same reason: because, though the property exists, it isn't what we want):

Example 7.14. get-viewport-size.js (excerpt)      
     
function getViewportSize()      
{      
 var size = [0, 0];      
     
 if (typeof window.innerWidth != 'undefined')      
 {      
   size = [      
       window.innerWidth,      
       window.innerHeight      
   ];      
 }      
 else if (typeof document.documentElement != 'undefined'      
     && typeof document.documentElement.clientWidth !=      
     'undefined' && document.documentElement.clientWidth != 0)      
 {      
   size = [      
       document.documentElement.clientWidth,      
       document.documentElement.clientHeight      
   ];      
 }      
 else      
 {      
   size = [      
       document.getElementsByTagName('body')[0].clientWidth,      
       document.getElementsByTagName('body')[0].clientHeight      
   ];      
 }      
     
 return size;      
}

The function returns an array of the width and height, so we can call it whenever we need that data:

Example 7.15. get-viewport-size.js (excerpt)      
     
window.onresize = function()      
{      
 var size = getViewportSize();      
 alert('Viewport size: [' + size[0] + ', ' + size[1] + ']');      
};

Summary

We've covered the basics of window and frame manipulation from a pragmatist's point of view in this chapter. We've also talked about principles and techniques that we can use to ensure that scripts like this are as user-friendly and as accessible as we can make them. Doubtless, this kind of work will remain controversial, and clearly we do need some kind of targeting mechanism, because even though the use of frames is slowly dying out, the advent of ever more sophisticated interfaces keeps these issues alive.

I rather like the XLink standard's show attribute, which has values like new and replace. These suggest a target process (open a new window, and replace the contents of the current window, respectively) but they don't actually define specific behaviors. They leave it up to the user agent to control what actually happens, so, for example, new could be used to open tabs instead of windows.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links