Chapter 8. Browser Pieces

8.0. Introduction

The browser objects include the window, navigator, screen, history, and location. They’re part of what is known as the Browser Object Model (BOM), or the DOM Level 0 set of objects, which also includes the document, frames, and various other elements. Typically though, when we think of browser objects, we think of the first five elements I just mentioned. In the HTML5 specification, these are known as the browsing context.

The topmost element is the window, and all the other browser objects are children. You can access the children elements using the window:

var browser = window.navigator.userAgent;

Or you can access the objects directly:

var browser = navigator.userAgent;

When working with the browser objects, be aware that until very recently, none of the objects was covered by a standard specification, and a consistent implementation was not guaranteed. Though most browsers share the same methods and properties, there are also browser-specific properties with each object. Make sure to check your target browsers’ documentation when you work with the objects, and test the pages in all of the browsers.

Work is underway with the W3C HTML5 effort to provide a user application object model, which does include all of the browser objects covered in this chapter. However, it’s all very new, and not broadly implemented at the time of this writing. It’s also subject to change, since the HTML5 effort is still ongoing. Where possible, I’ll note how an object or method is changing because of this new work.

8.1. Ask the Web Page Reader to Confirm an Action

Problem

You want to confirm an action with the web page reader.

Solution

Use the confirm pop-up box:

var answer = confirm("Are you sure you want to do that?");
if (answer == true) {
   alert("You're sure");
} else {
   alert("You decided against");
}

Discussion

There are three types of pop-up boxes in JavaScript. The alert pop up just provides a message, with an OK button to close the pop up:

alert("You're sure");

The confirm pop up, demonstrated in the solution, asks a question that is answered true by pushing the OK button, false if the Cancel button is pressed:

var answer = confirm("Are you sure?");

The last pop up is the prompt. You supply a string used for the prompt message, and can optionally provide a default response. The web page reader is given an input field to type a response, which is returned:

var answer = prompt("What's your name", "anonymous");

You’ll want to provide a default response, even if it’s only an empty string (""). Use caution when using the prompt pop up, as browser security could block the small window, or require user permission in order to work. Based on the newer levels of security, consider using forms, even for simple, one-answer questions.

8.2. Creating a New, Stripped-Down Browser Window

Problem

You want to open a web page in a new, stripped-down, fixed-size browser window.

Solution

Open a new browser window using the window.open method, passing in optional parameters: a URL to load, and a window name:

var newWindow = window.open("http://oreilly.com", "namedWindow");

Discussion

In older implementations, the window.open method takes three parameters: the URL, the window name, and the window features as a string of comma-delimited options. If the URL is omitted, the window opens with “about:blank”. If the name is not given, the default “_blank” is used, which means each window is a new window. If the features string is omitted, default formatting is used.

However, the window.open method is changing with HTML5. At the time this was written, the features string was no longer supported, and a fourth parameter, replace, is a Boolean value that indicates whether the URL replaces the contents in an open window, and removes the existing URL from the window’s history.

Note

Opening a new window can trigger pop up window prevention, and the default behavior can change from browser to browser. For instance, in Firefox 3.x, the new window is opened as a new tab by default, though you can configure it differently in the user preferences.

8.3. Finding Out About the Browser Accessing the Page

Problem

You want to determine information about the browser accessing the web page.

Solution

Use the Navigator object to discover information about the browser and browser environment:

var browser = navigator.userAgent;
var info = "<p>Browser: " + browser + "</p>";

var platform = navigator.platform;
info+="<p>Platform: " + platform + "</p>";

Discussion

The Navigator object has a wealth of information about the web page reader’s browser, as well as the operating system. The supported properties change from browser to browser, and several properties aren’t of much interest to the average JavaScript developer. At a minimum, most support the helpful properties shown in Table 8-1.

Table 8-1. Navigator properties

Property

Purpose

Example

appCodeName

Code name for the browser

Mozilla

appName

Official name of the browser

Netscape

appVersion

Browser version

5.0 (Macintosh; U; PPC Mac OS X 10_4_11; en) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9

cookieEnabled

Whether cookies are enabled

true if enabled, otherwise false

language

Language supported

en-US or en

platform

 

MacPPC

product

 

Gecko

userAgent

 

Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3

vendor

Vendor for browser

Apple Computer, Inc.

There are also some collections, such as mimeTypes and plugins.

It’s interesting to look at the Navigator object contents, and useful for checking important information such as whether cookies are enabled or not. It’s a bad decision, though, to use the Navigator object to detect the browser to determine which code to run.

Most browsers are constantly releasing new versions with new functionality. If you code for a browser, you have to change your code each time a browser releases a new browser. Plus it’s very easy to spoof a browser string, so the value is very unreliable. You’re better off using object, rather than browser, detection when coding cross-browser applications.

See Also

To see object detection in action, see Chapter 7, which focuses on event handling and features several instances of using object detection.

8.4. Warning the Web Page Reader About Leaving a Page

Problem

Your web page reader has clicked a link that takes him out of your website. Because of the sensitive nature of your site, such as a government, bank, or medical organization site, you want to make sure that the person is aware that he’s leaving your site and going to an external site.

Solution

Add an event listener for the window unload event and then provide a prompt to the web page reader that he’s leaving the site:

window.onunload=goodbye;

function goodbye() {
   alert("You're leaving our site. Thanks for stopping by!";
}

Discussion

We don’t want to indiscriminately throw up pop ups, or intervene with links or people going about their business, but there are times when we want to ensure that people know that when they click a link, type in a new domain, or even close the browser, they’re leaving the current site.

I’ve seen such actions in bank websites, the IRS’s site (U.S. tax organization), and other sites where the type of information being gathered can be quite confidential. You want people to be aware of the fact that they’re leaving the previously secure website and going somewhere that may be less than secure.

In particular, providing this functionality is helpful behavior if you provide links to external sites within your web pages. Because the link is in your pages, your web page readers may assume the external site is an offshoot of your own. If the other site resembles your site enough, the confusion can be exacerbated. Your readers may provide information to the new site only because they think there is a connection between your site and the external one.

8.5. Changing Stylesheets Depending on Color Support

Problem

You want to change your site’s stylesheet if the device viewing the page only supports 4-bit grayscale, or 8-bit color.

Solution

Use the Screen object to check the colorDepth property, and change either a CSS rule or an entire stylesheet based on the findings:

if (window.screen.colorDepth <= 8) {
   var style = document.documentElement.style ?
document.documentElement.style : document.body.style;
   style.backgroundColor="#ffffff";
}

Or:

function setActiveStyleSheet(title) {
  var a;
  for(var i=0; (a = document.getElementsByTagName("link")[i]); i++) {
     if(a.getAttribute("rel").indexOf("style") != -1
        && a.getAttribute("title")) {
       a.disabled = true;
       if(a.getAttribute("title") == title) a.disabled = false;
     }
  }
}
if (window.screen.colorDepth <= 4) {
   setActiveStyleSheet("dull");
}

Discussion

Once upon a time, most computer monitors only supported 8-bit, or 256-color monitors. If you used a CSS style color that was beyond what was considered a “web-safe” color, you couldn’t depend on the end result. Where you expected a delicate violet, you might get a washed-out, gray-like color instead.

How important is it to support web-safe colors these days? Not too important. Computer systems have been beyond the 256-color limit for well over a decade now, and most old 8-bit systems have gone on to their reward or been repurposed into nongraphical Linux boxes.

Still, there is a whole new breed of web-accessible tools now, including eReaders, like the Kindle, that are redefining what we think of when it comes to web display. The Kindle supports 4-bit grayscale, and it allows web access.

You can either change colors directly, as the solution demonstrates, or you can change the stylesheet. The latter is a better option if you use color and graphics extensively in your website.

The stylesheet switcher code in the solution was from an example I provided in my O’Reilly book Painting the Web, which is adapted from an article at A List Apart. The technology is very mature, as the date of the article (2001) demonstrates. Nowadays, people provide different stylesheets based on the use of the media attribute. Some examples of the media attribute are:

  • print, for printing

  • handheld, for handheld devices

  • screen, for typical computer monitors

  • tv, for television

  • braille, for Braille tactile devices

In addition, there’s an @media rule you can use directly in stylesheets to modify values based on media types. However, there may be cases where there is no media type or @media rule that works—or the result is quirky, at best.

Let’s examine the stylesheet switcher from the top. The for loop is controlled by accessing all of the link elements in the web page, which are then assigned, in turn, to a variable, a. In the loop, each is checked to see if it has a rel attribute set to style. If it does, and it has a title, and the title doesn’t match the stylesheet we want, we disable the stylesheet. However, if the title does match, then we enable the stylesheet.

See Also

For more on the getAttribute method, see Recipe 11.12. Lynda Weinmann is generally regarded as the first person who provided documentation of web-safe colors. You can see the web-safe palette and read more of the history of web-safe colors at http://www.lynda.com/resources/webpalette.aspx.

8.6. Modifying Image Dimensions Depending on Page Size

Problem

You want to set the screen width and serve up an appropriately sized photo.

Solution

Use the Screen object’s availWidth or width values to determine available space:

window.onload=function() {
  if (window.screen.availWidth >= 800) {
    var imgs = document.getElementsByTagName("img");
    for (var i = 0; i < imgs.length; i++) {
      var name = imgs[i].src.split("-");
      var newname = name[0] + "-big.jpg";
      imgs[i].src = newname;
    }
  }
}

Discussion

The ability to swap images in and out of the page has been around for years, and was a very common form of dynamic HTML at one time. It’s still popular with Ajax-based image libraries and slideshows.

The solution assumes that the website can be accessed in browsers or other user agents that are less than 800 pixels in width. It also assumes the images are provided in two sizes, and the names of the images are differentiated between those that are large (imgname-big.jpg), and small (imgname-thumb.jpg).

There are two horizontal properties available with Screen: availWidth and width. The width property provides information about the screen width, while the availWidth property provides information about the browser width. In the solution, I’m using availWidth.

In the solution, all of the img elements in the page are accessed, and for each, the name of the current image is accessed. The String split method is used to find the unique part of the image filename (before the dash), which is then concatenated to -big.jpg to set the image src attribute to the larger-sized image.

One of the disadvantages to this approach is that the person will see this switching going on. A way around this is to use CSS to modify the width of the image, say to 90% of the page size:

img
{
   max-width: 90%;
}

However, this doesn’t always generate a good image, as browsers don’t do the best job of creating smaller versions of images.

The solution also doesn’t take into account the bandwidth necessary to, first, load the larger image. That’s why it’s not unusual for websites just to load the smallest image into the web page by default, and then provide a link to the larger image, or use an image management JavaScript library to load larger images inline when clicking the thumbnail.

See Also

Probably one of the more popular image JavaScript libraries in use is LightBox2, available at http://www.huddletogether.com/projects/lightbox2/.

8.7. Creating Breadcrumbs in a CMS Template Page

Problem

You want to display footprint information based on the page URL for use in a Content Management System (CMS) template.

Solution

Use the window.location object to get information about the current page’s URL, and then break it down to provide the footprint information:

var items = location.pathname.substr(1).split("/");
var breadcrumbTrail = "<p>";
for (var i = 0; i < items.length; i++) {
  breadcrumbTrail+=" -> " + items[i];
}
breadcrumbTrail+="</p>";

Discussion

The window.location object can be accessed in JavaScript as just location. It has eight properties, all of which describe one aspect of the web page URL, as listed in Table 8-2.

Table 8-2. window.location object properties

Property

Description

Example

hash

Part of URL following #, including the #

#mysection

host

Hostname and optional port

burningbird.net or [burningbird.net]:80

hostname

Hostname

burningbird.net

href

Full URL

http://burningbird.net/jscb#test

pathname

Relative path name

/jscb#test

port

Port number

80

protocol

URL protocol

http:

search

Part of the URL following ?, including ?

?q=username

A breadcrumb trail is a reprint of the path relative to the hostname, broken into pieces that can then be accessed directly.

You could embed the breadcrumb directly into a web page, but many Content Management Systems (CMS), such as Drupal or Wordpress, use one page as a template for most of the site contents. For instance, Drupal has one page called page.tmpl.php, which serves as the template for most of the content of a Drupal site.

Though there are plug-ins and other applications that can embed a breadcrumb, sometimes it’s just as easy to use JavaScript to create the breadcrumb—especially if the navigation to the separate site components is also available in a menu (for accessibility purposes, or if JavaScript is turned off).

To demonstrate how to create a breadcrumb, Example 8-1 shows a breadcrumb application that not only parses out the different relative path components, but surrounds them with a subpath link and adds an arrow annotation.

Example 8-1. Building a breadcrumb trail with window.location
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>breadcrumb trail</title>
<script>
//<![CDATA[


window.onload=function() {
  // split relative path
  var items = location.pathname.substr(1).split("/");

  // build main path
  var mainpath = "<a href='" + location.protocol + "//" +
   location.hostname + "/";

  // begin breadcrumb
  var breadcrumbTrail = "<p>";
  for (var i = 0; i < items.length; i++) {

    // trailing slash
    if (items[i].length == 0 ) break;

    // extend main path for new level
    mainpath+=items[i];

    // add slash after all but last item
    if (i < items.length-1)
      mainpath+="/";

    // create breadcrumb component
    // add arrows for interior items only
    if (i > 0 && i < items.length)
      breadcrumbTrail+=" -> ";

    // add crumb
    breadcrumbTrail+= mainpath + "'>" + items[i] + "</a>";
  }

  // insert into page
  breadcrumbTrail+="</p>";
  document.getElementById("breadcrumb").innerHTML=breadcrumbTrail;

}

//--><!]]>
</script>
</head>
<body>
<div id="breadcrumb"></div>
</body>
</html>

The application adjusts for the first item in the list (no arrow), as well as the last (no final slash [/]), and includes handling cases where subdirectories have a trailing slash in the URL. There would be no list items for the main page, so no breadcrumb is printed out. You could adjust the script even further to not insert the empty paragraph element into the script when there are no items, or use different markup.

The script is simple, and works whether the page is statically or dynamically generated, as shown in Figure 8-1.

A JS-enabled breadcrumb application
Figure 8-1. A JS-enabled breadcrumb application

8.8. Bookmarking a Dynamic Page

Problem

You have a dynamic application in which updates occur in the page rather than when the page refreshes. You want to provide a link to your web page reader that not only takes the user back to the page, but returns it to a given state.

Solution

Use the Location object’s hash property in order to return to the state directly:

var someval = window.location.hash.split("#")[1];
if (someval == "state1") {
...
}

Discussion

A page fragment (#somevalue) isn’t just a way to annotate an in-page link; it can also be a way to restore a state for an application. If dynamic effects are being made in a web page, and you want to preserve state at any time in such a way that a person can return to that state, you can create a unique hash for it and then provide the link to your web page reader.

To demonstrate, Example 8-2 is a web page that has a div wrapping a button and another div, and which changes various CSS-style property attributes with each click of the button. Since we want to give the web page reader the ability to return to a state at any time, a link is generated for that the person.

Why don’t I use a stylesheet class and change the class name, rather than set the attributes individually? And why call all of the functions in order, rather than directly call each one? The reason is I’m building on the style settings from each previous function call, rather than resetting the style with each. If I used a class setting with the values, the results would be vastly different, because the style attributes not set would be returned to their inherited values. That’s the major difference between using a class setting and changing individual style attributes.

Example 8-2. Preserving page state through hash on link
<!DOCTYPE html>
<head>
<title>Remember me?</title>
<script>

   window.onload=function() {

      // set up button
      document.getElementById("next").onclick=nextPanel;

      // check for hash, if found, reload state
      var hash = window.location.hash.split("#")[1];
      switch (hash) {
        case "one" :
           functionOne();
           break;
        case "two" :
           functionOne();
           functionTwo();
           break;
        case "three" :
           functionOne();
           functionTwo();
           functionThree();
      }

   }

   // display next panel, based on button's class
   function nextPanel() {
      var classNm = this.getAttribute("class");
      switch(classNm) {
         case "zero" :
            functionOne();
            break;
         case "one" :
            functionTwo();
            break;
         case "two" :
            functionThree();
       }
   }
   // set both the button class, and create the state link,
   // add to page
   function setPage(page) {
      document.getElementById("next").setAttribute("class",page);
      var link = document.getElementById("link");
      var path = location.protocol + "//" + location.hostname + "/" +
                 location.pathname + "#" + page;
      link.innerHTML="<p><a href='" + path + "'>link</a></p>";

   }

   // function one, two, three - change div, set button and link
   function functionOne() {
      var square = document.getElementById("square");
      square.style.backgroundColor="#ff0000";
      square.style.width="200px";
      square.style.height="200px";
      square.style.padding="10px";
      square.style.margin="20px";
      setPage("one");
   }

   function functionTwo() {
      var square = document.getElementById("square");
      square.style.backgroundColor="#ffff00";
      square.style.position="absolute";
      square.style.left="200px";
      setPage("two");
   }

   function functionThree() {
      var square = document.getElementById("square");
      square.style.width="400px";
      square.style.height="400px";
      square.style.backgroundColor="#00ff00";
      square.style.left="400px";
      setPage("three");
   }
</script>
</head>
<body>
<button id="next" class="zero">Next Action</button>
<div id="square">
<p>This is the object</p>
<div id="link"></div>
</div>
</body>

In the code, if there is a hash mark given in the window.object, the functions to set the state of the page are called. Since, in this example, each is dependent on what’s happened in the other state functions, they’ll need to be called before the end state. So if the hash mark is three (#three), both functionOne and functionTwo need to be called before functionThree, or the div element’s state will be different (the style settings won’t be the same).

Note

The use of set- and getAttribute with the style attribute won’t work with IE7. The downloadable example contains a workaround.

See Also

Recipe 12.15 provides a detailed discussion about changing CSS properties for elements. Chapters 13 and 14 demonstrate other dynamic page effects created by setting CSS-style attributes or changing an element’s class name. Recipe 20.1 covers another example in which information can be persisted using the URL. Recipe 20.3 demonstrates state persistence using new HTML5 functionality.

Recipe 8.9 extends the concept of state from Example 8-2, so that the example can also work with page refreshes and after the use of the back button. Recipe 11.12 covers the getAttribute method.

8.9. Preserving State for Back Button, Page Refresh

Problem

You want to store a dynamic page’s effects in such a way that if the web page reader accidentally hits the page refresh or back button, the effects aren’t lost.

Solution

You set the location object’s hash to preserve state automatically, so a dynamic effect is preserved:

// get state
var someval = window.location.hash.split("#")[1];
if (someval == "state1") {
...
}
// set state
function setPage(page) {
  location.hash=page;
}

Discussion

In Recipe 8.8, links are provided for the web page reader to return to a page state at a later time. The same principle can be used to capture a page state at any time, in case the person accidentally refreshes a page.

To modify Example 8-2 to maintain state automatically, all you need to do is modify the setPage function:

function setPage(page) {
  document.getElementById("next").setAttribute("class",page);
  location.hash=page;
}

Rather than build a link and add to the page, the page is added as a hash to location, which also serves to add the link, with hash, to the page history. With this change, if the person accidentally does a refresh at any point in time, the page will be returned to the exact state it was in before she did the refresh.

The use of location and unique hashes to record dynamic state has also been used for the infamous back button problem when it comes to dynamic or Ajax-based applications.

With most browsers, if you add a new hash with each state change, and then click the back button, you’ll see the address bar reflect the change. What you probably won’t see, though, is the actual state of the change reflected in the page. That’s because even if you change the location in the address bar, you’re not triggering a page reload. And there is no way to capture an address change in order to reload the page.

A workaround to this problem is to add a timer that fires at certain intervals, triggering code that checks the current location object and, in turn, triggers a page reload so the page reflects the address bar. Frankly, I’m not overly fond of this approach.

I do like the idea of capturing state because of accidental page refreshes, or providing a bookmark link. I haven’t found repairing the back button to be a serious concern. At least, not until the state of technology is such that it can occur without having to use such things as timers.

See Also

See Recipe 8.8 for Example 8-2.

Speaking of the state of technology, Recipe 20.3 introduces the new HTML5 history object method, pushState, and associated window.onpopevent event handler. These maintain state and were created to help resolve the back button problem mentioned in this recipe. Recipe 12.15 covers the setAttribute method.

Get JavaScript Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.