In this section we will introduce the objects of the BOM that are common to all browsers.
In Chapter 4, we saw that JavaScript has a number of native objects that we have access to and can make use of. Most of the objects are those that we need to create ourselves, such as the String and Date objects. Others, such as the Math object, exist without us needing to create them and are ready for use immediately when the page starts loading.
When JavaScript is running in a web page, it has access to a large number of other objects made available by the web browser. Rather like the Math object, these are created for us rather than us needing to create them explicitly. As mentioned, the objects, their methods, properties, and events are all mapped out in something called the Browser Object Model (BOM).
The BOM is very large and potentially overwhelming at first. However, you'll find that initially you won't be using more than 10 percent of the available objects, methods, and properties in the BOM. We'll start in this chapter by looking at the more commonly used parts of the BOM, shown in Figure 5-1. These parts of the BOM are, to a certain extent, common across all browsers. Later chapters will build on this so that by the end of the book you'll be able to really make the BOM work for you.
The BOM has a hierarchy. At the very top of this hierarchy is the window object. You can think of this as representing the frame of the browser and everything associated with it, such as the scrollbars, navigator bar icons, and so on.
Contained inside our window frame is the page. The page is represented in the BOM by the document
object. You can see this represented in Figure 5-2.
We'll now go on to discuss each of these objects in more detail.
The window object represents the browser's frame or window in which your web page is contained. To some extent, it also represents the browser itself and includes a number of properties that are there simply because they don't fit anywhere else. For example, via the properties of the window object, we can find out what browser is running, the pages the user has visited, the size of the browser window, the size of the user's screen, and much more. We can also use the window object to access and change the text in the browser's status bar, change the page that is loaded, and even open new windows.
The window object is a global object, which means we don't need to use its name to access its properties and methods. In fact, the global functions and variables (the ones accessible to script anywhere in a page) are all created as properties of the global object. For example, the alert() function we have been using since the beginning of the book is, in fact, the alert() method of the window object. Although we have been using this simply as
alert("Hello!");
we could write
window.alert("Hello!");
However, since the window object is the global object, it is perfectly correct to use the first version.
Some of the properties of the window object are themselves objects. Those common to both IE and NN include the document, navigator, history, screen, and location objects. The document object represents our page, the history object contains the history of pages visited by the user, the navigator object holds information about the browser, the screen object contains information about the display capabilities of the client, and the location object contains details on the current page's location. We'll look at these important objects individually later in the chapter.
Let's start with a nice, simple example in which we change the default text shown in the browser's status bar. The status bar (usually in the bottom left of the browser window) is usually used by the browser to show the status of any document loading into the browser. For example, on IE, once a document has loaded, you'll normally see "Done" in the status bar, and in Navigator, it normally says "Document : Done." Let's change that so it says "Hello and Welcome."
To change the default message in the window's status bar, we need to use the window object's defaultStatus property. To do this we can write
window.defaultStatus = "Hello and Welcome";
or, because the window is the global object, we can just write
defaultStatus = "Hello and Welcome";
Either way works, and both are valid, though personally I normally prefer to write window in front because it makes clear exactly where the defaultStatus property came from. Otherwise we might think that defaultStatus is a variable name. This is particularly true when using less common properties and methods, such as defaultStatus. For properties like document or methods like alert(), you'll find that you become so familiar with them that you don't need to put window in front to remind you of their context.
Let's put our code in a page.
<html> <head> <script language="JavaScript" type="text/JavaScript"> window.defaultStatus = "Hello and Welcome"; </script> </head> </html>
Save the page as ch5_examp1.htm and load it into your browser. You should see the specified message in the status bar.
At this point, it's worth highlighting the point that within a web page you shouldn't use names for your functions or variables that conflict with names of BOM objects or their properties and methods. If you do, you may not get an error, but instead get unexpected results. For example, in the following code we declare a variable named defaultStatus. We then try to set the defaultStatus property of the window object to "Welcome to my website." However, this won't change the default message in the status bar; instead the value in the defaultStatus variable will be changed.
var defaultStatus; defaultStatus = "Welcome to my website";
In this situation we need to either use a different variable name or add window in front of the defaultStatus property we want to change.
var defaultStatus; window.defaultStatus = "Welcome to my website";
This will have the effect that we want, that is, the message in the status bar will change. However, this way of writing things can obviously make code very confusing, so it is usually best not to use BOM object, property, or method names as variable names.
As with all the BOM objects, there are lots of properties and methods we could look at for the window object. However, in this chapter we'll concentrate on the history, location, navigator, screen, and document properties. All five of these properties contain objects (the history, location, navigator, screen, and document objects), each with its own properties and methods. In the next few pages, we'll look at each of these objects in turn and find out how they can help us make full use of the BOM.
The history object keeps track of each page that the user visits. This list of pages is commonly called the history stack for the browser. It enables the user to click the browser's Back and Forward buttons to revisit pages. We have access to this object via the window object's history property.
Like the native JavaScript Array object, the history object has a length property. This can be used to find out how many pages are in the history stack.
As you might expect, the history object has the back() and forward() methods. When they are called, the location of the page currently loaded in the browser is changed to the previous or next page that the user has visited.
The history object also has the go() method. This takes one parameter that specifies how far forward or backward in the history stack you want to go. For example, if you wanted to return the user to the page before the previous page, you'd write
history.go(-2);
To go forward three pages, you'd write
history.go(3);.
Note that go(-1) and back() are equivalent, as are go(1) and forward().
The location object contains lots of potentially useful information about the current page's location. Not only does it contain the URL (Uniform Resource Locator) for the page, but also the server hosting the page, the port number of the server connection, and the protocol used. This information is made available through the location object's href, hostname, port, and protocol properties. However, many of these values are only really relevant when we are loading the page from a server and not, as we are doing in our examples at present, loading the page directly from a local hard drive.
In addition to retrieving the current page's location, we can also use the methods of the location object to change the location and refresh the current page.
We can navigate to another page in two ways. We can either set the location object's href property to point to another page, or we can use the location object's replace() method. The effect of the two is the same; the page changes location. However, they differ in that the replace() method removes the current page from the history stack and replaces it with the new page we are moving to, whereas using the href property simply adds the new page to the top of the history stack. This means that if the replace() method has been used and the user clicks the Back button in the browser, the user can't go back to the original page loaded. If the href property has been used, the user can use the Back button as normal.
For example, to replace the current page with a new page called myPage.htm, we'd use the replace() method and write
window.location.replace("myPage.htm");
This will load myPage.htm and replace any occurrence of the current page in the history stack with myPage.htm.
To load the same page and to add it to the history of pages navigated to, we use the href property
window.location.href = "myPage.htm";
and the page currently loaded is added to the history. In both cases I've added window at the front of the expression, but as the window object is global throughout the page, we could have written the following:
location.replace("myPage.htm");
or
location.href = "myPage.htm";
The navigator object is another object that is a property of the window object and is available in both IE and NN browsers, despite the name. Its name is more of a historical thing, rather than being very descriptive. Perhaps a better name would be the "browser object," because the navigator object contains lots of information about the browser and the operating system in which it's running.
Probably the most common use of the navigator object is for handling browser differences. Using its properties, we can find out which browser, version, and operating system the user has. We can then act on that information and make sure the user is directed to pages that will work with his browser. The last section in this chapter is dedicated to this important subject, so we will not discuss it further here.
The screen object property of the window object contains a lot of information about the display capabilities of the client machine. Its properties include the height and width properties, which indicate the vertical and horizontal range of the screen in pixels.
Another property of the screen object, which we will be using in an example later, is the colorDepth property. This tells us the number of bits used for colors on the client's screen.
Along with the window object, the document object is probably one of the most important and commonly used objects in the BOM. Via this object you can gain access to the properties and methods of some of the objects defined by HTML tags inside your page.
Unfortunately, it's here that the BOMs of IE 4 and 5 and NN 4 differ greatly. The former's BOM implementation allows you easy access to nearly every element on a page, whereas the latter's only allows limited access to some elements. If we go back to earlier browsers, such as IE 3 and NN 3, we find the BOM and our ability to program with it even more limited. We will go into the details of the differences between the BOMs of IE 4.0+ and NN 4.x in Chapter 12.
While IE 5, 5.5, and 6 support all the proprietary BOM of IE 4 (that is, it is backward compatible), NN 6 does not support all the proprietary BOM of NN 4. Instead, Netscape 6 and 7 support the W3C DOM, also somewhat supported by IE 5, 5.5, and 6. We will leave this more advanced topic until Chapter 13.
In this chapter we will concentrate on those properties and methods of the document object that are common to all browsers.
The document object has a number of properties associated with it, which are also arrays. The main ones are the forms, images, and links arrays. IE 4 and IE 5 support a number of other array properties, such as the all array property, which is an array of all the tags represented by objects in the page. Netscape also has a few additional arrays that are not supported by IE, for example the tags array. However, we'll be concentrating on using objects that have cross-browser support, so that we are not limiting our web pages to just one browser.
We'll be looking at the images and links arrays shortly. A third array, the forms array, will be one of the topics of the next chapter when we look at forms in web browsers. First, though, we'll look at a nice, simple example of how to use the document object's methods and properties.
We've already come across some of the document object's properties and methods, for example the write() method and the bgColor property. Appendix C has a full list of the available properties and methods, and we'll be using more of them later in the book.
In the next example we set the background color of the page to a color dependent on how many colors the user's screen supports. This is termed screen color depth. If the user has a display that supports just two colors (black and white), there's no point in us setting the background color to bright red. We accommodate this by using JavaScript to set a color the user can actually see.
<html> <body> <script language="JavaScript" type="text/JavaScript"> switch (window.screen.colorDepth) { case 1: case 4: document.bgColor = "white"; break; case 8: case 15: case 16: document.bgColor = "blue"; break; case 24: case 32: document.bgColor = "skyblue"; break; default: document.bgColor = "white"; } document.write("Your screen supports " + window.screen.colorDepth + "bit color"); </script> </body> </html>
Save the page as ch5_examp2.htm. When you load it into your browser, the background color of the page will be determined by your current screen color depth. Also, a message in the page will tell you what the color depth currently is.
You can test that the code is working properly by changing the colors supported by your screen. On Windows, you can do this by right-clicking on the desktop and choosing the Properties option. Under the Settings tab, there is a section called Colors in which you can change the number of colors supported. By refreshing the browser, you can see what difference this makes to the color of the page.
Note |
On Netscape 6, it's necessary to shut down and restart the browser to observe any effect. |
As we saw earlier, the window object has the screen object property. One of the properties of this object is the colorDepth property, which returns a value of 1, 4, 8, 15, 16, 24, or 32. This represents the number of bits assigned to each pixel on your screen—a pixel is just one of the many dots that your screen is made up of. To work out how many colors we have, we just calculate the value of 2 to the power of the colorDepth property. For example, a colorDepth of 1 means that there are two colors available, a colorDepth of 8 means that there are 256 colors available, and so on. Currently, most people have a screen color depth of at least 8, but usually of 16 or 24, with 32 becoming more common.
The first task of our script block is to set the color of the background of the page based on the number of colors the user can actually see. We do this in a big switch statement. The condition that is checked for in the switch statement is the value of window.screen.colorDepth.
switch (window.screen.colorDepth)
We don't need to set a different color for each colorDepth possible, because many of them are not that different for general web use. Instead, we set the same background color for different, but similar, colorDepth values. For a colorDepth of 1 or 4, we set the background to white. We do this by declaring the case 1: statement, but we don't give it any code. If the colorDepth matches this case statement, it will fall through to the case 4: statement below, where we do set the background color to white. We then call a break statement, so that the case matching will not fall any further through the switch statement.
{ case 1: case 4: document.bgColor = "white"; break;
We do the same with colorDepth values of 8, 15, and 16, setting the background color to blue as follows:
case 8: case 15: case 16: document.bgColor = "blue"; break;
Finally, we do the same for colorDepth values of 24 and 32, setting the background color to skyblue.
case 24: case 32: document.bgColor = "skyblue"; break;
We end the switch statement with a default case, just in case the other case statements did not match. In this default case, we again set the background color to white.
default: document.bgColor = "white"; }
In our next bit of script, we use the document object's write() method, something we've been using in our examples for a while now. We use it to write to the document—that is, the page—the number of bits the color depth is currently set at, as follows:
document.write("Your screen supports " + window.screen.colorDepth + "bit color")
We've already been using the document object in our examples throughout the book so far. We used its bgColor property in Chapter 1 to change the background color of the page, and we've also made good use of its write() method in our examples to write HTML and text out to the page.
Now let's look at some of the slightly more complex properties of the document object. These properties have in common the fact that they all contain arrays. The first one we look at is an array containing an object for each image in the page.
As we know, we can insert an image into an HTML page using the following tag:
<img ALT="USA" name=myImage src="usa.gif">
The browser makes this image available for us to script in JavaScript by creating an img object for it with the name myImage. In fact, each image on your page has an img object created for it.
Each of the img objects in a page is stored in the images[] array. This array is a property of the document object. The first image on the page is found in the element document.images[0], the second in document.images[1], and so on.
If we want to, we can assign a variable to reference an img object in the images[] array. It can make code easier to read. For example, if we write
var myImage2 = document.images[1];
the myImage2 variable will contain a reference to the img object inside the images[] array at index position 1. Now we can write myImage2 instead of document.images[1] in our code, with exactly the same effect.
We can also access img objects in the images array by name. For example, the img object created by the <img> tag, which has the name myImage, can be accessed in the document object's images array property like this:
document.images["myImage"]
Because the document.images property is an array, it has the properties of the native JavaScript Array object, such as the length property. For example, if we want to know how many images there are on the page, the code document.images.length will tell us.
The img object itself has a number of useful properties we can utilize. The most important of these is its src property. By changing this we can change the image that's loaded. We demonstrate this in the next example.
<html> <body> <img name=img1 src="" border=0 width=200 height=150> <script language="JavaScript" type="text/JavaScript"> var myImages = new Array("usa.gif","canada.gif","jamaica.gif","mexico.gif"); var imgIndex = prompt("Enter a number from 0 to 3",""); document.images["img1"].src = myImages[imgIndex]; </script> </body> </html>
Save this as ch5_examp3.htm. You will also need four image files, called usa.gif, canada.gif, jamaica.gif, and mexico.gif. You can create these yourself or obtain the ones provided with the code download for the book.
When this page is loaded into the browser, a prompt box asks you to enter a number from 0 to 3. A different image will be displayed depending on the number you enter.
At the top of the page we have our HTML <img> tag. Notice that the src attribute is left empty and it is given the name value img1.
<img name=img1 src="" border=0 width=200 height=150>
Next we come to the script block where the image to be displayed is decided. On the first line we define an array containing a list of image sources. My images are in the same directory as the HTML file, so I haven't specified a path. If yours are not, make sure you enter the full path (for example, C:\myImages \mexico.gif).
Then we ask the user for a number from 0 to 3, which will be used as the array index to access the image source in the myImages array.
var imgIndex = prompt("Enter a number from 0 to 3","");
Finally we set the src property of the img object to the source text inside the myImages array element with the index number provided by the user.
document.images["img1"].src = myImages[imgIndex];
Don't forget that when we write document.images["img1"], we are accessing the img object stored in the images array. We've used the image's name, as defined in the name attribute of the <img> tag, but we could have used document.images[0]. It's an index position of 0, because it's the first (and only) image on this page.
For each hyperlink tag <A> defined with an href attribute, the browser creates an A object. The most important property of the A object is the href property, corresponding to the href attribute of the tag. Using this, we can find out where the link points to and can change this, even after the page has loaded.
The collection of all A objects in a page is contained within the links[] array, in a similar way to the img objects in the images[] array that we saw earlier.
In Chapter 4 when we introduced objects, we said that they were defined by their methods and properties. However, this is not the whole story. Objects also have events associated with them. We did not mention it before since native JavaScript objects do not have these events, but the objects of the BOM do.
Events occur when something in particular happens. For example, the user clicking on the page, clicking on a hyperlink, or moving his mouse pointer over some text all cause events to occur. Another example, which is used quite frequently, is the load event for the page.
Why are we interested in events?
Take as an example the situation where we want to make a menu pop up when the user clicks anywhere in our web page. Assuming that we can write a function that will make the popup menu appear, how do we know when to make it appear, or in other words, when to call the function? We somehow need to intercept the event of the user clicking in the document, and make sure our function is called when that event occurs.
To do this, we need to use something called an event handler. We associate this with the code that we want to execute when the event occurs. This provides us with a way of intercepting events and making our code execute when they have occurred. You will find that adding an event handler to our code is often known as "connecting our code to the event." It's a bit like setting an alarm clock—you set the clock to make a ringing noise when a certain event happens. With alarm clocks, the event is when a certain time is reached.
Event handlers are made up of the word on and the event that they will handle. For example, the click event has the onclick event handler, and the load event has the onload event handler.
A number of ways exist to connect your code to an event using event handlers; we're going to look at two of the more useful ways.
The first and most common method is to add the event handler's name and the code we want to execute to the HTML tag's attributes.
Let's create a simple HTML page with a single hyperlink, given by the element <A>. Associated to this element is the A object. One of the events the A object has is the click event. The click event fires, not surprisingly, when the user clicks on the hyperlink.
<html> <body> <A href="somepage.htm" name="linkSomePage"> Click Me </A> </body> </html>
As it stands this page does nothing a normal hyperlink doesn't do. You click it, and it navigates this window to another page, called somepage.htm, which would need to be created. There's been no event handler added to the link—yet!
As we mentioned, one very common and easy way of connecting the event to our code is to add it directly to the tag of the object whose event we are capturing. In this case, it's the click event of the A object, as defined by the <A> tag. On clicking the link, we want to capture the event and connect it to our code. We need to add the event handler, in this case onclick, as an attribute to our <A> tag. We set the value of the attribute to the code we want executing when the event occurs.
Let's rewrite our <A> tag to do this as follows:
<A href="somepage.htm" name="linkSomePage" onclick="alert('You Clicked?')">
Click Me
</A>
You can see that we have added onclick="alert('You Clicked?')" to the definition of the <A> tag. Now, when the link is clicked, we see an alert box. After this, the hyperlink does its usual stuff and takes us to the page defined in the href attribute.
This is fine if you just have one line of code to connect to the event handler, but what if you want a number of lines to execute when the link is clicked?
Well, all you need to do is define the function you want to execute and call it in the onclick code. Let's do that now.
<html> <body> <script language="JavaScript"> function linkSomePage_onclick() { alert('You Clicked?'); return true; } </script> <A href="somepage.htm" name="linkSomePage" onclick="return linkSomePage_onclick()"> Click Me </A> </body> </html>
Within the script block we have created a function, just a standard function, and given it a descriptive name to help us when reading the code. I'm using ObjectName event() as my function name. That way you can instantly see what object on the page this relates to and which event is being connected to. So, in the preceding example, the function is called linkSomePage_onclick() since we are referring to the onclick event handler for the A object with name linkSomePage. Note that this naming convention is simply something created by me; it's not compulsory, and you can use whatever convention you prefer as long as you are consistent.
Our onclick attribute is now connected to some code that calls the function linkSomePage_onclick(). Therefore, when the user clicks the hyperlink, this function will be executed.
You'll also see that the function returns a value, true in this case. Also, where we define our onclick attribute, we return the return value of the function by using the return statement before the function name. Why do we do this?
The value returned by onclick="return linkSomePage_onclick()" is used by JavaScript to decide whether the normal action of the link, that is, going to a new page, should occur. If we return true, the action continues, and we go to somepage.htm. If we return false, the normal chain of events (that is, going to somepage.htm) does not happen. We say that the action associated with the event is canceled. Try changing the function to this:
function linkSomePage_onclick() { alert('This link is going nowhere'); return false; }
Now you'll find that you just get a message, and no attempt is made to go to somepage.htm.
Not all objects and their events make use of the return value, so sometimes it's redundant. Also, it's not always the case that returning false cancels the action. For reasons of browser history rather than logic, it's sometimes true that cancels the action. Generally speaking, it's best to return true and deal with the exceptions to this as you find them.
Some events are not directly linked with the user's actions as such. For example, the window object has the load event, which fires when a page is loaded, and the unload event, which fires when the page is unloaded (that is, either the user closes the browser or moves to another page).
Event handlers for the window object actually go inside the <body> tag. For example, to add an event handler for the load and unload events, we'd write the following:
<body language=JavaScript onload="myOnLoadfunction()" onunload="myOnUnloadFunction()">
Notice that we have specified the language attribute of the <body> tag as JavaScript. This is because the <body> tag is not contained within a JavaScript script block defined with the <script> tag. As usual, since JavaScript is the default scripting language, this can be left off.
I promised you two ways of connecting to events, so now let's look at the second.
In this way of connecting to events, we first need to define the function that will be executed when the event occurs. Then we need to set that object's event handler property to the function we defined.
This is illustrated in the following example.
<html> <body> <script language="JavaScript"> function linkSomePage_onclick() { alert('This link is going nowhere'); return false; } </script> <A href="somepage.htm" name="linkSomePage"> Click Me </A> <script language="JavaScript" type="text/JavaScript"> window.document.links[0].onclick = linkSomePage_onclick; </script> </body> </html>
Save this as ch5_examp4.htm.
We define the function linkSomePage_onclick(), much as we did before. As before we can return a value indicating whether we want the normal action of that object to happen.
Next we have the <A> tag, whose object's event we are connecting to. You'll notice there is no mention of the event handler or the function within the attributes of the tag.
The connection is made between the object's event and our function on the final lines of script, as shown in the following:
<script language="JavaScript" type="text/JavaScript"> document.links[0].onclick = linkSomePage_onclick; </script>
As we saw before, document.links[0] returns the A object corresponding to the first link in our web page, which is our linkSomePage hyperlink. We set this object's onclick property to reference our function name—this makes the connection between the object's event handler and our function. Note that no parentheses are added after the function name. Now whenever we click the link, our function gets executed.
The first method of connecting code to events is easier, so why would we ever want to use the second?
Perhaps the most common situation in which you would want to do this is where you want to capture an event for which there is no HTML tag to write your event handler as an attribute. It is also useful in situations where you want the code attached to an event handler to be changed dynamically.
Let's look at another example in which we connect to a hyperlink's click event to randomly change the image loaded in a page.
<html> <head> <script language="JavaScript"> var myImages = new Array("usa.gif","canada.gif","jamaica.gif","mexico.gif"); function changeImg(imgNumber) { var imgClicked = document.images[imgNumber]; var newImgNumber = Math.round(Math.random() * 3); while (imgClicked.src.indexOf(myImages[newImgNumber]) != -1) { newImgNumber = Math.round(Math.random() * 3); } imgClicked.src = myImages[newImgNumber]; return false; } </script> </head> <body> <A href="" name="linkImg1" onclick="return changeImg(0)"> <img name=img0 SRC="usa.gif" border=0 > </A> <A href="" name="linkImg2" onclick="return changeImg(1)"> <img name="img0" SRC="mexico.gif" border="0" > </A> </body> </html>
Save the page as ch5_examp5.htm. Again, you will need four image files for the example, which you can create or retrieve from the code download available with this book.
Load the page into your browser. You should see a page like that shown in Figure 5-3.
If you click on an image, you'll see it change to a different image, and that image is selected randomly.
The first line in the script block at the top of the page defines a variable with page level scope. This is an array that contains our list of image sources.
var myImages = new Array("usa.gif","canada.gif","jamaica.gif","mexico.gif");
Next we have the changeImg() function, which will be connected to the onclick event handler of an <A> tag surrounding each of our images. This time we are using the same function for both links' onclick event handlers and indeed can connect one function to as many event handlers as we like. We pass this function one parameter—the index in the images array of the img object related to that click event, so that we know which image we need to act on.
In the first line of the function, we use the passed parameter to declare a new variable that points to the img object in the images[] array corresponding to the image that was clicked, as follows:
function changeImg(imgNumber) { var imgClicked = document.images[imgNumber];
Following this, we set newImgNumber variable to a random integer between 0 and 3. The Math.random() method provides a random number between 0 and 1, and we multiply that by 3 to get a number between 0 and 3. This number is converted to an integer (0, 1, 2, or 3) using Math.round(). This integer will provide the index for the image src that we will select from the myImages array.
var newImgNumber = Math.round(Math.random() * 3);
The next lines are a while loop, the purpose of which is to ensure we don't select the same image as the current image. If the string contained in myImages[newImgNumber] is found inside the src property of the current image, we know it's the same and that we need to get another random number. We keep looping until we get a new image, at which point myImages[newImgNumber] will not be found in the existing src and -1 will be returned by the indexOf() method, breaking out of the loop.
while (imgClicked.src.indexOf(myImages[newImgNumber]) != -1) { newImgNumber = Math.round(Math.random() * 3); }
Finally, we set the src property of the img object to the new value contained in our myImages array. We return false to stop the link from trying to navigate to another page—the link is only there to provide a way of capturing an onclick event handler.
imgClicked.src = myImages[newImgNumber]; return false; }
In IE and Netscape 6 and 7 you'll find that the img object itself has an onclick event, but unfortunately in earlier Netscape browsers, this is not so. To make our code Netscape-compatible, we need to use workarounds—the goal is the same, just a little more convoluted. Since the A object does support the onclick event handler, we wrap the image inside <A> tags and use the A object's onclick event handler instead, as follows:
<A href="" name="linkImg1" onclick="return changeImg(0)"> <img name=img1 src="usa.gif" border=0 > </A>
If we were using only IE or Netscape 6 and 7, we could just use <img> tags and add onclick event handlers like this:
<img name=img1 src="usa.gif" border=0 ID="img1" onclick="return changeImg(0)" >
Many browsers, versions of the same browser, and operating systems are out there on the Internet, each with its own version of the BOM and its own particular quirks. It's therefore important that you make sure your pages will work correctly on all browsers, or at least degrade gracefully, such as by displaying a message suggesting that the user upgrade their browser.
Although you can go a long way with cross-browser-compatible code, there may come a time when you want to add extra features that only one browser supports. The solution is to write script that checks the browser name, version and, if necessary, operating system and executes script that is compatible with these variants.
There are two main ways of checking for browser details. The first is to see if the object and property we use in our code is actually available in the user's browser. Let's say for example our code relies on the document object's all property, a property available to browsers like IE version 4 to 6 and Opera 7, but not to any Netscape browsers. If we write
If (document.all) { // our code using the document.all property }
the if statement's condition will evaluate to true if the property returns a valid value; if the property is not supported, its value will be undefined, and the if statement will evaluate to false. To check whether a particular method is supported, we can do this:
if (document.getElementById) { // code } else { // Alternative code }
We've "tested" the existence of the method as we did with properties. Just remember not to include the opening or closing brackets after the method even if it normally has a number of parameters. The getElementById method for example has one parameter.
We see in the following example how to find a property that is supported only by browsers that also support the method we are interested in. It's not foolproof but it works most of the time.
<html> <body> <script> var browser = "Unknown"; var version = "0"; // NN4+ if (document.layers) { browser = "NN"; version = "4.0"; if (navigator.securityPolicy) { version = "4.7+"; } } else if (document.all) { browser = "IE" version = "4" } // IE5+ if (window.clipboardData) { browser = "IE" version = "5+" } // NN6+ else if (window.sidebar) { browser = "NN"; version = "6+"; } document.write(browser + " " + version); </script> </body> </html>
Save this example as ch5_example6.htm. The page looks at which BOM and JavaScript properties the browser supports and based on that, makes a rough guess as to the browser type. So on the first lines, we check to see if the browser's document object has the layers property.
if (document.layers) { browser = "NN"; version = "4.0"; if (navigator.securityPolicy) { version = "4.7+"; } }
Because only NN 4 supports the layers property, we can assume this is an NN 4 browser or at least a browser that supports the NN 4 layers feature. The inner if statement looks to see if the navigator object supports the securityPolicy property; this property is supported by only Netscape Navigator version 4 and in particular only by sub-version 4.7 and later. However, the slight danger here is that a rare non– Netscape or Microsoft browser might support the securityPolicy property but not other features of NN 4.7+ browsers. It's wise to test with as many varieties of browser as you expect to be visiting the website and see what happens. If one fails, we can see which property it fails on and write code to check for it.
Next we have a test for the document object's all property, a property supported by IE 4 through 6 and Opera 7.
} else if (document.all) { browser = "IE" version = "4" }
To see if it's an IE 5+ browser, check the window object's clipboardData property, which is only currently supported by IE 5+.
// IE5+ if (window.clipboardData) { browser = "IE" version = "5+" }
The final bit of checking is for the sidebar property supported by NN 6 and 7 that deals with the Netscape sidebar tool.
// NN6+ else if (window.sidebar) { browser = "NN"; version = "6+"; }
On the last line we write out the results of the BOM property checking.
document.write(browser + " " + version);
Hopefully, this example demonstrates how to use object checking to see if a particular feature is supported by a browser. In the example we haven't actually used the various features; it's simply a way of demonstrating how to check for browser-specific objects. When writing your own code, be sure to double-check whether a particular feature you're using is supported by all the browsers you expect to visit your website. If some of the browsers you expect to visit don't support a particular feature, then test for the feature and write alternative code for the browsers that don't support it.
Sometimes people switch off JavaScript in their browsers, or possibly they are using a browser that doesn't support JavaScript, though that's quite rare these days. To cover this situation, we can use the <noscript> tag. Any HTML inside the <noscript> tag will be displayed only to browsers that either don't support JavaScript or where JavaScript has been disabled:
<html> <body> <noscript> This website requires JavaScript to be enabled. </noscript> </body> <html>
Our second method of checking browser details is the navigator object property of the window object. In particular, we use the appName and userAgent properties of the navigator object. The main problem with this method is that less common browsers may well declare themselves to be a particular version of Internet Explorer or Netscape Navigator but not actually support all the JavaScript or BOM objects, properties, or methods of that browser. Therefore this method of "browser sniffing" has fallen out of favor and is not the recommended way of checking for compatibility.
The appName property returns the model of the browser, such as Microsoft Internet Explorer or Netscape.
The userAgent property returns a string containing various bits of information, such as the browser version, operating system, and browser model. However, the value returned by this property varies from browser to browser, so we have to be careful when using it. For example, we can't assume that it starts with the browser version. It does under NN, but under IE the browser version is embedded in the middle of the string.
In this example, we create a page that uses the above-mentioned properties to discover the client's browser, version, and operating system. The page can then forward the user to a page that matches the client's specifications.
<html> <head> <script language="JavaScript" type="text/JavaScript"> function getBrowserName() { var lsBrowser = navigator.appName; if (lsBrowser.indexOf("Microsoft") >= 0) { lsBrowser = "MSIE"; } else if (lsBrowser.indexOf("Netscape") >= 0) { lsBrowser = "NETSCAPE"; } else { lsBrowser = "UNKNOWN"; } return lsBrowser; } function getOS() { var userPlat = "unknown"; var navInfo = navigator.userAgent; if ((navInfo.indexOf("windows NT") != -1) || (navInfo.indexOf("windows 95") != -1 ) || (navInfo.indexOf("windows 98") != -1 ) || (navInfo.indexOf("WinNT") != -1 ) || (navInfo.indexOf("Win95") != -1 ) || (navInfo.indexOf("Win98") != -1 )) { userPlat = "Win32"; } else if(navInfo.indexOf("Win16") != -1) { userPlat = "Win16"; } else if(navInfo.indexOf("Macintosh") != -1) { userPlat = "PPC"; } else if(navInfo.indexOf("68K") != -1) { userPlat = "68K"; } return userPlat; } function getBrowserVersion() { var findIndex; var browserVersion = 0; var browser = getBrowserName(); if (browser == "MSIE") { browserVersion = navigator.userAgent; findIndex = browserVersion.indexOf(browser) + 5; browserVersion = parseInt(browserVersion.substring(findIndex,findIndex + 1)); } else { browserVersion = parseInt(navigator.appVersion.substring(0,1)); } return browserVersion; } </script> </head> <body> <script language="JavaScript" type="text/JavaScript"> var userOS = getOS(); var browserName = getBrowserName(); var browserVersion = getBrowserVersion(); if (browserVersion < 4 || browserName == "UNKNOWN" || userOS == "Win16") { document.write("<h2>Sorry this browser version is not supported</h2>") } else if (browserName == "NETSCAPE") { location.replace("NetscapePage.htm"); } else { location.replace("MSIEPage.htm"); } </script> <noscript> <h2>This website requires a browser supporting scripting</h2> </noscript> </body> </html>
Save the script as ch5_examp6.htm. You'll also need to create two more simple pages, one called MSIEPage.htm for IE 4 and 5 browsers, and one called NetscapePage.htm for Netscape 4 and above. These are just dummy pages, which we redirect to. In practice they would be your browser-specific pages, implementing code that simply can't be changed to be cross-browser-compatible.
If the code differences are small, it is possible to incorporate them in one page and put them inside if statements, making sure the code is run only by a suitable browser.
If the browser detected is not version 4 or above; it's a Win16 operating system, such as Windows 3.1; or it's not a Microsoft or Netscape browser, we display a message informing the user that the pages are not compatible with their browser. Although this is not ideal, it's certainly better than just letting the pages fail with a battery of error messages.
The script block in the head of the page defines three important functions. The getBrowserName() function finds out the name of the browser, the getOS() function returns the operating system, and the getBrowserVersion() function finds out the browser version.
Let's look at the first of these, getBrowserName(). First we get the name of the browser, as returned by navigator.appName, and store it in the variable lsBrowser. This will also be used as the variable to store the return value for the function.
function getBrowserName() { var lsBrowser = navigator.appName;
The string returned by this property tends to be quite long and does vary slightly sometimes. However, by checking for the existence of certain keywords, such as Microsoft or Netscape, we can determine the browser name. We start with the following lines:
if (lsBrowser.indexOf("Microsoft") >= 0) { lsBrowser = "MSIE"; }
which search the lsBrowser string for Microsoft. If the indexOf value of this substring is zero or greater, we know we have found it, and so we set the return value to MSIE.
The following else if statement does the same, except that we have modified it for Netscape.
else if (lsBrowser.indexOf("Netscape") >= 0) { lsBrowser = "NETSCAPE"; }
If neither of these two is found, we return UNKNOWN as the browser name. If you want to support a wider range of browsers, you could add extra if statements to handle other browsers.
else { lsBrowser = "UNKNOWN"; }
The value of lsBrowser is then returned to the calling code.
return lsBrowser; }
Next we'll turn to the getOS() function. The function starts by defining two variables: userPlat is initialized to the string unknown, and navInfo is initialized to the value of the userAgent property of the navigator object.
function getOS() { var userPlat = "unknown"; var navInfo = navigator.userAgent;
The sort of information the userAgent property returns for Internet Explorer 5 is shown in Figure 5-4.
Likewise, the sort of information the userAgent property returns for Netscape Navigator 4.7 is shown in Figure 5-5.
Even though these were both running on the same operating system, namely Windows 2000, you can see that the results returned by two different browsers for userAgent are very different.
The next part of the function consists of a giant if statement where we test the userAgent property for the name of various operating systems.
The first part of the if statement is checking for all the possible Windows permutations, such as "windows NT," "WinNT," "windows 98," "Win98," and so on, by searching navInfo with the indexOf() method. If any of the 32-bit operating systems of Microsoft are found, we set our return value to Win32.
if ((navInfo.indexOf("windows NT") != -1) || (navInfo.indexOf("windows 95") != -1 ) || (navInfo.indexOf("windows 98") != -1 ) || (navInfo.indexOf("WinNT") != -1 ) || (navInfo.indexOf("Win95") != -1 ) || (navInfo.indexOf("Win98") != -1 )) { userPlat = "Win32"; }
Our next else if statement checks to see if Win16 is anywhere in the userAgent string; if so, the user has a Win16 operating system such as Windows 3.1.
else if(navInfo.indexOf("Win16") != -1) { userPlat = "Win16"; }
The final two if statements are checking for variations of Apple's Mac operating systems. We could go further and check for Unix and Linux.
else if(navInfo.indexOf("Macintosh") != -1) { userPlat = "PPC"; } else if(navInfo.indexOf("68K") != -1) { userPlat = "68K"; }
Finally if no match is found, the return value will be unknown.
return userPlat; }
How important is it to check for an operating system? Unfortunately IE on Win32 is not the same as IE on the Mac or on Win16. The same goes for NN, though perhaps to a lesser extent. For example, the Mac version of IE does not support ActiveX controls. However, most of the time you don't need to use the OS checking code—it tends to be the more complex or exotic things that won't work on different operating systems.
We now turn to the final function, getBrowserVersion().
How we deduce the browser version depends on the type of browser. For Netscape this is simply the first character of the Navigator object's appVersion property. However for IE this does not distinguish between version 4 and version 5, so we need a different approach.
For these reasons, our first task in the function is to find out which browser we are dealing with. We declare and initialize the browser variable to the name of the browser, using the getBrowserName() function we just wrote.
function getBrowserVersion() { var findIndex; var browserVersion = 0; var browser = getBrowserName();
If the browser is MSIE (Internet Explorer), we need to use the userAgent property again. Under IE the userAgent property always contains MSIE followed by the browser version. So what we need to do is search for MSIE, then get the number following that.
We set findIndex to the character position of MSIE, plus 5, which selects the number after MSIE. browserVersion is set to the integer value of that number, which we obtain using the substring() method. This selects the character starting at findIndex, our number, and whose end is one before findIndex + 1. This ensures that we just select one character.
if (browser == "MSIE") { browserVersion = navigator.userAgent; findIndex = browserVersion.indexOf(browser) + 5; browserVersion = parseInt(browserVersion.substring(findIndex,findIndex + 1)); }
Finding out the browser version number for Netscape and other browsers is easier; we just convert the first character in appVersion to an integer. The first character is obtained using substring() method, starting at character 0 and ending one before character 1, as follows:
else { browserVersion = parseInt(navigator.appVersion.substring(0,1)); }
At the end of the function we return browserVersion to the calling code as shown in the following:
return browserVersion; }
We've seen the supporting functions, but how do we make use of them? Well, in the following code, which executes as the page is loaded, we obtain the three bits of information: operating system, browser, and version, and we use this to filter out unsupported browsers.
<script language="JavaScript" type="text/JavaScript"> var userOS = getOS(); var browserName = getBrowserName(); var browserVersion = getBrowserVersion(); if (browserVersion < 4 || browserName == "UNKNOWN" || userOS == "Win16") { document.write("<h2>Sorry this browser version is not supported</h2>") } else if (browserName == "NETSCAPE") { location.replace("NetscapePage.htm"); } else { location.replace("MSIEPage.htm"); } </script>
The first if statement says that if the browser version is less than 4 or it's an unknown browser or the user is running a 16-bit version of Windows, we write a message into the page telling the user that her browser is not supported. We could, if we wanted, give more exact details.
Knowing that we are dealing with a version 4 browser or better, we then check if the browser is Netscape. If it is, we redirect the user to a Netscape-specific page using the replace() method of the location object. Then in the else statement, we redirect to the Internet Explorer–compatible page.
<noscript> <h2>This website requires a browser supporting scripting</h2> </noscript>
Finally, I've added some <noscript> tags for early browsers and for users who have chosen to disable JavaScript. These will display a message informing the user that she needs a JavaScript-enabled browser.