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
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
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.
8.3. Finding Out About the Browser Accessing the 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.
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 printinghandheld
, for handheld devicesscreen
, for typical computer monitorstv
, for televisionbraille
, 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
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.
Property | Description | Example |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
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.
<!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.
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.
<!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).
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.