12
The Browser Object Model

WHAT'S IN THIS CHAPTER?

  • Understanding the window object, the core of the BOM
  • Controlling windows and pop-ups
  • Page information from the location object
  • Using the navigator object to learn about the browser
  • Manipulating the browser history stack with the history object

WROX.COM DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples for this chapter are available as a part of this chapter's code download on the book's website at www.wrox.com/go/projavascript4e on the Download Code tab.

Though ECMAScript describes it as the core of JavaScript, the Browser Object Model (BOM) is really the core of using JavaScript on the web. The BOM provides objects that expose browser functionality independent of any web page content. For years, a lack of any real specification made the BOM both interesting and problematic because browser vendors were free to augment it as they saw fit. The commonalities between browsers became de facto standards that have survived browser development mostly for the purpose of interoperability. Part of the HTML5 specification now covers the major aspects of the BOM, as the W3C seeks to standardize one of the most fundamental parts of JavaScript in the browser.

THE WINDOW OBJECT

At the core of the BOM is the window object, which represents an instance of the browser. The window object serves a dual purpose in browsers, acting as the JavaScript interface to the browser window and the ECMAScript Global object. This means that every object, variable, and function defined in a web page uses window as its Global object and has access to methods like parseInt().

The Global Scope

Because the window object doubles as the ECMAScript Global object, all variables and functions declared globally with var become properties and methods of the window object. Consider this example:

var age = 29;
var sayAge = () => alert(this.age);
      
alert(window.age); // 29
sayAge();          // 29
window.sayAge();   // 29

Here, a variable named age and a function named sayAge() are defined in the global scope, which automatically places them on the window object. Thus, the variable age is also accessible as window.age, and the function sayAge() is also accessible via window.sayAge(). Because sayAge() exists in the global scope, this.age maps to window.age, and the correct result is displayed.

If, instead, let or const is substituted for var, the default attachment to the global object does not occur:

let age = 29;
const sayAge = () => alert(this.age);
      
alert(window.age); // undefined
sayAge();          // undefined
window.sayAge();   // TypeError: window.sayAge is not a function

Another thing to keep in mind: attempting to access an undeclared variable throws an error, but it is possible to check for the existence of a potentially undeclared variable by looking on the window object. For example:

// this throws an error because oldValue is undeclared
var newValue = oldValue;
// this doesn't throw an error, because it's a property lookup
// newValue is set to undefined
var newValue = window.oldValue;

Keeping this in mind, there are many objects in JavaScript that are considered to be global, such as location and navigator (both discussed later in the chapter), but are actually properties of the window object.

Window Relationships

The top object always points to the very top (outermost) window, which is the browser window itself. Another window object is called parent. The parent object always points to the current window's immediate parent window. For the topmost browser window, parent is equal to top (and both are equal to window). The topmost window will never have a value set for name unless the window was opened using window.open(), as discussed later in this chapter.

There is one final window property, called self, which always points to window. The two can, in fact, be used interchangeably. Even though it has no separate value, self is included for consistency with the top and parent objects.

Each of these objects is a property of the window object, accessible via window.parent, window.top, and so on. This means it's possible to chain window objects together, such as window.parent.parent.

Window Position and Pixel Ratio

The position of a window object may be determined and changed using various properties and methods. Modern browsers all provide screenLeft and screenTop properties that indicate the window's location in relation to the left and top of the screen, respectively, in CSS pixels.

It is also possible to move the window to a new position using the moveTo() and moveBy() methods. Each method accepts two arguments. moveTo() expects the x and y coordinates to move to an absolute coordinate, and moveBy() expects the number of pixels to move relative to the current coordinate. These methods are demonstrated here:

// move the window to the upper-left coordinate
window.moveTo(0,0);
      
// move the window down by 100 pixels
window.moveBy(0, 100);
      
// move the window to position (200, 300)
window.moveTo(200, 300);
      
// move the window left by 50 pixels
window.moveBy(-50, 0);

Depending on the browser, these methods may be conditionally or completely disabled.

Pixel Ratios

A "CSS pixel" is the denomination of the pixel used universally in web development. It is defined as an angular measurement: 0.0213°, approximately 1/96 of an inch on a device held at arm's length. The purpose of this definition is to give the pixel size a uniform meaning across devices: for example, 12px font (measured in CSS pixels) on a low-resolution tablet should appear to be the same size as a 12px (measured in CSS pixels) font on a high-resolution 4k monitor. Therein lies a problem: such a system requires an inherent scaling factor required to convert from physical pixels (the actual resolution of the display) to CSS pixels (the virtual resolution reported to the web browser).

For example, a phone screen might have a physical resolution of 1920x1080, but because these pixels are incredibly small the web browser will scale this resolution down to a smaller logical resolution, such as 640x360. This scaling factor is provided to the browser as window.devicePixelRatio. For a device converting from 1920x1080 to 640x360, the devicePixelRatio would be reported as 3. In this way, a 12px font in physical pixels would really be a 36px font in logical pixels (or CSS pixels).

This devicePixelRatio is effectively an analogue of DPI (dots per inch). DPI is effectively recording the same information, but devicePixelRatio offers it as a unitless ratio.

Window Size

Determining the size of a window cross-browser is not straightforward. All modern browsers provide four properties: innerWidth, innerHeight, outerWidth, and outerHeight. outerWidth and outerHeight return the dimensions of the browser window itself (regardless of whether it's used on the topmost window object or on a frame). The innerWidth and innerHeight properties indicate the size of the page viewport inside the browser window (minus borders and toolbars).

The document.documentElement.clientWidth and document.documentElement.clientHeight properties provide the width and height of the page viewport.

The end result is that there's no accurate way to determine the size of the browser window itself, but it is possible to get the dimensions of the page viewport, as shown in the following example:

let pageWidth = window.innerWidth,
  pageHeight = window.innerHeight;
 
if (typeof pageWidth != "number") {
 if (document.compatMode == "CSS1Compat"){
  pageWidth = document.documentElement.clientWidth;
  pageHeight = document.documentElement.clientHeight;
 } else {
  pageWidth = document.body.clientWidth;
  pageHeight = document.body.clientHeight;
 }
}

In this code, pageWidth and pageHeight are assigned initial values of window.innerWidth and window.innerHeight, respectively. A check is then done to see if the value of pageWidth is a number; if not, then the code determines if the page is in standards mode by using document.compatMode. If it is, then document.documentElement.clientWidth and document.documentElement.clientHeight are used; otherwise, document.body.clientWidth and document.body.clientHeight are used.

For mobile devices, window.innerWidth and window.innerHeight are the dimensions of the visual viewport, which is the visible area of the page on the screen. Mobile Internet Explorer doesn't support these properties but provides the same information on document.documentElement.clientWidth and document.documentElement.clientHeight. These values change as you zoom in or out of a page.

In other mobile browsers, the measurements of document.documentElement provide measurements for the layout viewport, which are the actual dimensions of the rendered page (as opposed to the visual viewport, which is only a small portion of the entire page). Mobile Internet Explorer stores this information in document.body.clientWidth and document.body.clientHeight. These values do not change as you zoom in and out.

Because of these differences from desktop browsers, you may need to first determine if the user is on a mobile device before deciding which measurements to use and honor.

The browser window can be resized using the resizeTo() and resizeBy() methods. Each method accepts two arguments: resizeTo() expects a new width and height, and resizeBy() expects the differences in each dimension. Here's an example:

// resize to 100 x 100
window.resizeTo(100, 100);
      
// resize to 200 x 150
window.resizeBy(100, 50);
      
// resize to 300 x 300
window.resizeTo(300, 300);

As with the window-movement methods, the resize methods may be disabled by the browser and are disabled by default in some browsers. Also like the movement methods, these methods apply only to the topmost window object.

Window Viewport Position

Because the browser window is usually not large enough to display the entire rendered document at once, the user is given the ability to scroll around the document with a limited viewport. The CSS pixel offset of the current viewport is available as an X and Y pair, representing the number of pixels the viewport is currently scrolled in that direction. The X and Y offsets are each accessible via two properties, which return identical values: window.pageXoffset / window.scrollX, and window.pageYoffset / window.scrollY.

It is also possible to explicitly scroll the page by a certain amount using several different window methods. These methods are passed two coordinates indicating how far in the X and Y direction the viewport should scroll. window.scroll(x, y) will scroll the viewport by a relative amount. (window.scrollBy(x, y) behaves identically). window.scrollTo(x, y) scrolls the viewport to an absolute offset.

// Scroll down 100px relative to the current viewport
window.scroll(0, 100);

// Scroll right 40px relative to the current viewport
Window.scroll(40, 0);

// Scroll to the top left corner of the page
// window.scrollTo(0, 0);

// Scroll to 100px from the top and left of the page
// window.scrollTo(100, 100);

These methods also accept a ScrollToOptions dictionary, which in addition to holding the offset values can instruct the browser to smooth the scroll via the behavior property.

// normal scroll
window.scrollTo({
 left: 100,
 top: 100,
 behavior: 'auto'
});

// smooth scroll 
window.scrollTo({
 left: 100,
 top: 100,
 behavior: 'smooth'
});

Navigating and Opening Windows

The window.open() method can be used both to navigate to a particular URL and to open a new browser window. This method accepts four arguments: the URL to load, the window target, a string of features, and a Boolean value indicating that the new page should take the place of the currently loaded page in the browser history. Typically only the first three arguments are used; the last argument applies only when not opening a new window.

If the second argument passed to window.open() is the name of a window or frame that already exists, then the URL is loaded into the window or frame with that name. Here's an example:

// same as <a href="http://www.wrox.com" target="topFrame"/>
window.open("http://www.wrox.com/", "topFrame");

This line of code acts as if the user clicked a link with the href attribute set to "http://www.wrox.com" and the target attribute set to "topFrame". If there is a window named "topFrame", then the URL will be loaded there; otherwise, a new window is created and given the name "topFrame". The second argument may also be any of the special window names: _self, _parent, _top, or _blank.

Popping Up Windows

When the second argument doesn't identify an existing window, a new window or tab is created based on a string passed in as the third argument. If the third argument is missing, a new browser window (or tab, based on browser settings) is opened with all of the default browser window settings. (Toolbars, the location bar, and the status bar are all set based on the browser's default settings.) The third argument is ignored when not opening a new window.

The third argument is a comma-delimited string of settings indicating display information for the new window. The following table describes the various options.

SETTING VALUE(S) DESCRIPTION
fullscreen "yes" or "no" Indicates that the browser window should be maximized when created. Internet Explorer only.
height Number The initial height of the new window. This cannot be less than 100.
left Number The initial left coordinate of the new window. This cannot be a negative number.
location "yes" or "no" Indicates if the location bar should be displayed. The default varies based on the browser. When set to "no", the location bar may be either hidden or disabled (browser-dependent).
menubar "yes" or "no" Indicates if the menu bar should be displayed. The default is "no".
resizable "yes" or "no" Indicates if the new window can be resized by dragging its border. The default is "no".
scrollbars "yes" or "no" Indicates if the new window allows scrolling if the content cannot fit in the viewport. The default is "no".
status "yes" or "no" Indicates if the status bar should be displayed. The default varies based on the browser.
toolbar "yes" or "no" Indicates if the toolbar should be displayed. The default is "no".
top Number The initial top coordinate of the new window. This cannot be a negative number.
width Number The initial width of the new window. This cannot be less than 100.

Any or all of these settings may be specified as a comma-delimited set of name-value pairs. The name-value pairs are indicated by an equal sign. (No white space is allowed in the feature string.) Consider the following example:

window.open("http://www.wrox.com/", 
      "wroxWindow",
      "height=400,width=400,top=10,left=10,resizable=yes");

This code opens a new resizable window that's 400x400 and positioned 10 pixels from the top and left of the screen.

The window.open() method returns a reference to the newly created window. This object is the same as any other window object except that you typically have more control over it. For instance, some browsers that don't allow you to resize or move the main browser window by default may allow you to resize or move windows that you've created using window.open(). This object can be used to manipulate the newly opened window in the same way as any other window, as shown in this example:

let wroxWin = window.open("http://www.wrox.com/",
              "wroxWindow",
              "height=400,width=400,top=10,left=10,resizable=yes");
      
// resize it
wroxWin.resizeTo(500, 500);
      
// move it
wroxWin.moveTo(100, 100);

It's possible to close the newly opened window by calling the close() method as follows:

wroxWin.close();

This method works only for pop-up windows created by window.open(). It's not possible to close the main browser window without confirmation from the user. It is possible, however, for the pop-up window to close itself without user confirmation by calling top.close(). Once the window has been closed, the window reference still exists but cannot be used other than to check the closed property, as shown here:

wroxWin.close();
alert(wroxWin.closed); // true

The newly created window object has a reference back to the window that opened it via the opener property. This property is defined only on the topmost window object (top) of the pop-up window and is a pointer to the window or frame that called window.open(). For example:

let wroxWin = window.open("http://www.wrox.com/",
              "wroxWindow",
              "height=400,width=400,top=10,left=10,resizable=yes");
      
alert(wroxWin.opener === window); // true

Even though there is a pointer from the pop-up window back to the window that opened it, there is no reverse relationship. Windows do not keep track of the pop-ups that they spawn, so it's up to you to keep track if necessary.

Some browsers, try to run each tab in the browser as a separate process. When one tab opens another, the window objects need to be able to communicate with one another, so the tabs cannot run in separate processes. These browsers allow you to indicate that the newly created tab should be run in a separate process by setting the opener property to null, as in the following example:

let wroxWin = window.open("http://www.wrox.com/",
              "wroxWindow",
              "height=400,width=400,top=10,left=10,resizable=yes");
      
wroxWin.opener = null;

Setting opener to null indicates to the browser that the newly created tab doesn't need to communicate with the tab that opened it, so it may be run in a separate process. Once this connection has been severed, there is no way to recover it.

Security Restrictions

Pop-up windows went through a period of overuse by advertisers online. Pop-ups were often disguised as system dialogs to get the user to click on an advertisement. Because these pop-up web pages were styled to look like system dialogs, it was unclear to the user whether the dialog was legitimate. To aid in this determination, browsers began putting limits on the configuration of pop-up windows.

Early versions of Internet Explorer implemented multiple security features on pop-up windows, including not allowing pop-up windows to be created or moved offscreen and ensuring that the status bar cannot be turned off. Beginning with Internet Explorer 7, the location bar cannot be turned off and pop-up windows can't be moved or resized by default. Firefox 1 turned off the ability to suppress the status bar, so all pop-up windows have to display the status bar regardless of the feature string passed into window.open(). Firefox 3 forces the location bar to always be displayed on pop-up windows. Opera opens pop-up windows only within its main browser window but doesn't allow them to exist where they might be confused with system dialogs.

Additionally, browsers will allow the creation of pop-up windows only after a user action. A call to window.open() while a page is still being loaded, for instance, will not be executed and may cause an error to be displayed to the user. Pop-up windows may be opened based only on a click or a key press.

Pop-up Blockers

All modern browsers have pop-up–blocking software built in. The result is that most unexpected pop-ups are blocked. When a pop-up is blocked, one of two things happens. If the browser's built-in pop-up blocker stopped the pop-up, then window.open() will most likely return null. In that case, you can tell if a pop-up was blocked by checking the return value, as shown in the following example:

let wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null){
 alert("The popup was blocked!");
}

When a browser add-on or other program blocks a pop-up, window.open() typically throws an error. So to accurately detect when a pop-up has been blocked, you must check the return value and wrap the call to window.open() in a try-catch block, as in this example:

let blocked = false;
      
try {
 let wroxWin = window.open("http://www.wrox.com", "_blank");
 if (wroxWin == null){
  blocked = true;
 }
} catch (ex){
  blocked = true;
}
      
if (blocked){
  alert("The popup was blocked!");
}

This code accurately detects if a pop-up blocker has blocked the call to window.open(), regardless of the method being used.

Intervals and Timeouts

JavaScript execution in a browser is single-threaded but does allow for the scheduling of code to run at specific points in time through the use of timeouts and intervals. Timeouts execute some code after a specified amount of time, whereas intervals execute code repeatedly, waiting a specific amount of time in between each execution.

You set a timeout using the window's setTimeout() method, which accepts two arguments: the code to execute and the time (in milliseconds) to wait before scheduling the callback function to be executed. The first argument can be either a string containing JavaScript code (as would be used with eval()) or a function. For example:

// schedules an alert to show after 1 second
setTimeout(() => alert("Hello world!"), 1000);

The second argument, the number of milliseconds to wait, is not necessarily when the specified code will execute. JavaScript is single-threaded and, as such, can execute only one piece of code at a time. To manage execution, there is a queue of JavaScript tasks to execute. The tasks are executed in the order in which they were added to the queue. The second argument of setTimeout() tells the JavaScript engine to add this task onto the queue after a set number of milliseconds. If the queue is empty, then that code is executed immediately; if the queue is not empty, the code must wait its turn.

When setTimeout() is called, it returns a numeric ID for the timeout. The timeout ID is a unique identifier for the scheduled code that can be used to cancel the timeout. To cancel a pending timeout, use the clearTimeout() method and pass in the timeout ID, as in the following example:

// set the timeout
let timeoutId = setTimeout(() => alert("Hello world!"), 1000);
      
// cancel it
clearTimeout(timeoutId);

As long as clearTimeout() is called before the specified amount of time has passed, a timeout can be canceled completely. Calling clearTimeout() after the code has been executed has no effect.

Intervals work in the same way as timeouts except that they schedule the code for execution repeatedly at specific time intervals until the interval is canceled or the page is unloaded. The setInterval() method lets you set up intervals, and it accepts the same arguments as setTimeout(): the code to execute (string or function) and the milliseconds to wait between addition of the callback function to the execution queue. Here's an example:

   
setInterval(() => alert("Hello world!"), 10000);

The setInterval() method also returns an interval ID that can be used to cancel the interval at some point in the future. The clearInterval() method can be used with this ID to cancel all pending intervals. This ability is more important for intervals than timeouts since, if left unchecked, they continue to execute until the page is unloaded. Here is a common example of interval usage:

let num = 0, intervalId = null;
let max = 10;
      
let incrementNumber = function() {
 num++;
      
 // if the max has been reached, cancel all pending executions
 if (num == max) {
  clearInterval(intervalId);
  alert("Done");
 }
}
      
intervalId = setInterval(incrementNumber, 500);

In this example, the variable num is incremented every half second until it finally reaches the maximum number, at which point the interval is canceled. This pattern can also be implemented using timeouts, as shown here:

let num = 0;
let max = 10;
      
let incrementNumber = function() {
 num++;
      
 //if the max has not been reached, set another timeout
 if (num < max) {
   setTimeout(incrementNumber, 500);
 } else {
   alert("Done");
 }
}
      
setTimeout(incrementNumber, 500);

Note that when you're using timeouts, it is unnecessary to track the timeout ID because the execution will stop on its own and continue only if another timeout is set. This pattern is considered a best practice for setting intervals without actually using intervals. True intervals are rarely used in production environments because the time between the end of one interval and the beginning of the next is not necessarily guaranteed, and some intervals may be skipped. Using timeouts, as in the preceding example, ensures that can't happen. Generally speaking, it's best to avoid intervals.

System Dialogs

The browser is capable of invoking system dialogs to display to the user through the alert(), confirm(), and prompt() methods. These dialogs are not related to the web page being displayed in the browser and do not contain HTML. Their appearance is determined by operating system and/or browser settings rather than CSS. Additionally, each of these dialogs is synchronous and modal, meaning code execution stops when a dialog is displayed, and resumes after it has been dismissed.

The alert() method has been used throughout this book. It simply accepts a string to display to the user. Unlike console.log, which can accept a variable number of arguments and display them all at once, alert expects only one argument. When alert() is called, a system message box displays the specified text to the user, followed by a single OK button. If alert() is passed an argument that is not a string primitive, it will coerce the argument into a string with its .toString() method.

Alert dialogs are typically used when users must be made aware of something that they have no control over, such as an error. A user's only choice is to dismiss the dialog after reading the message. As shown in Figure 12-1.

Screenshot of a dialog box displaying a message “Hello world”, with the only option to click the OK button.

FIGURE 12-1

The second type of dialog is invoked by calling confirm(). A confirm dialog looks similar to an alert dialog in that it displays a message to the user. The main difference between the two is the presence of a Cancel button along with the OK button, which allows the user to indicate if a given action should be taken. For example, confirm("Are you sure?") displays the confirm dialog box shown in Figure 12-2.

To determine if the user clicked OK or Cancel, the confirm() method returns a Boolean value: true if OK was clicked, or false if Cancel was clicked or the dialog box was closed by clicking the X in the corner. Typical usage of a confirm dialog looks like this:

if (confirm("Are you sure?")) {
  alert("I'm so glad you're sure!");
} else {
   alert("I'm sorry to hear you're not sure.");
}

In this example, the confirm dialog is displayed to the user in the first line, which is a condition of the if statement. If the user clicks OK, an alert is displayed saying, “I'm so glad you're sure!” If, however, the Cancel button is clicked, an alert is displayed saying, “I'm sorry to hear you're not sure.” This type of pattern can be employed when the user tries to delete something, such as an e-mail message. Because the dialog will totally disrupt the user's experience on the page, this should be reserved for only actions that have dire consequences.

Screenshot of a dialog box displaying a message “Are you sure?”, with the options of OK and Cancel buttons.

FIGURE 12-2

The final type of dialog is displayed by calling prompt(), which prompts the user for input. Along with OK and Cancel buttons, this dialog has a text box where the user may enter some data. The prompt() method accepts two arguments: the text to display to the user, and the default value for the text box (which can be an empty string). Calling prompt("What is your name?", "Jake") results in the dialog box shown in Figure 12-3.

Screenshot of a dialog box displaying a calling prompt “What is your name?”, “Jake”, with the options of OK and Cancel buttons.

FIGURE 12-3

If the OK button is clicked, prompt() returns the value in the text box; if Cancel is clicked or the dialog is otherwise closed without clicking OK, the function returns null. Here's an example:

let result = prompt("What is your name? ", "");
if (result !== null) {
  alert("Welcome, " + result);
}

These system dialogs can be helpful for displaying information to the user and asking for confirmation of decisions. Because they require no HTML or CSS, they are fast and easy ways to enhance a web application.

Many browsers have introduced a special feature regarding these system dialogs. If the actively running script produces two or more system dialogs during its execution, each subsequent dialog after the first displays a check box that allows the user to disable any further dialogs until the page reloads.

When the check box is checked and the dialog box is dismissed, all further system dialogs (alerts, confirms, and prompts) are blocked until the page is reloaded. The developer is given no indication as to whether the dialog was displayed. The dialog counter resets whenever the browser is idle, so if two separate user actions produce an alert, the check box will not be displayed in either; if a single user action produces two alerts in a row, the second will contain the check box.

Two other types of dialogs can be displayed from JavaScript: find and print. Both of these dialogs are displayed asynchronously, returning control to the script immediately. The dialogs are the same as the ones the browser employs when the user selects either Find or Print from the browser's menu. These are displayed using the find() and print() methods on the window object as follows:

// display print dialog
window.print();
      
// display find dialog
window.find();

These two methods give no indication as to whether the user has done anything with the dialog, so it is difficult to make good use of them. Furthermore, because they are asynchronous, they don't contribute to the browser's dialog counter and won't be affected by the user opting to disallow further dialogs.

THE LOCATION OBJECT

One of the most useful BOM objects is location, which provides information about the document that is currently loaded in the window, as well as general navigation functionality. The location object is unique in that it is a property of both window and document; both window.location and document.location point to the same object. Not only does location know about the currently loaded document, but it also parses the URL into discrete segments that can be accessed via a series of properties. These properties are enumerated in the following table (the location prefix is assumed).

If the browser was currently located at http://foouser:[email protected]:80/WileyCDA/?q=javascript#contents, then the location object would behave as follows:

PROPERTY NAME RETURN VALUE DESCRIPTION
location.hash "#contents" The URL hash (the pound sign followed by zero or more characters), or an empty string if the URL doesn't have a hash.
location.host www.wrox.com:80 The name of the server and port number if present.
location.hostname www.wrox.com The name of the server without the port number.
location.href http://www.wrox.com:80/WileyCDA/?q=javascript#contents The full URL of the currently loaded page. The toString() method of location returns this value.
location.pathname "/WileyCDA/" The directory and/or filename of the URL.
location.port "80" The port of the request if specified in the URL. If a URL does not contain a port, then this property returns an empty string.
location.protocol "http:" The protocol used by the page. Typically "http:" or "https:".
location.search "?q=javascript" The query string of the URL. It returns a string beginning with a question mark.
location.username "foouser" The username specified before the domain name.
location.password "barpassword" The password specified before the domain name.
location.origin http://www.wrox.com The origin of the URL. Read only.

Query String Arguments

Most of the information in location is easily accessible from these properties. The one part of the URL that isn't provided is an easy-to-use query string. Though location.search returns everything from the question mark until the end of the URL, there is no immediate access to query-string arguments on a one-by-one basis. The following function parses the query string and returns an object with entries for each argument:

let getQueryStringArgs = function() {
 // get query string without the initial '?'
 let qs = (location.search.length> 0 ? location.search.substring(1) : ""),
   // object to hold data
   args = {};
 
 // assign each item onto the args object
 for (let item of qs.split("&").map(kv => kv.split("="))) {
  let name = decodeURIComponent(item[0]),
   value = decodeURIComponent(item[1]);
  if (name.length) {
   args[name] = value;
  }
 }
 
 return args;
}

The first step in this function is to strip off the question mark from the beginning of the query string. This happens only if location.search has one or more characters. The arguments will be stored on the args object, which is created using object-literal format. Next, the query string is split on the ampersand character, returning an array of strings in the format name=value. The for loop iterates over this array and then splits each item on the equal sign, returning an array where the first item is the name of the argument and the second item is the value. The name and value are each decoded using decodeURIComponent() (because query-string arguments are supposed to be encoded). Last, the name is assigned as a property on the args object, and its value is set to value. This function is used as follows:

// assume query string of ?q=javascript&num=10
      
let args = getQueryStringArgs();
      
alert(args["q"]);  // "javascript"
alert(args["num"]); // "10"

Each of the query-string arguments is now a property on the returned object, which provides fast access to each argument.

URLSearchParams

URLSearchParams offers a collection of utility methods which allow you to inspect and modify query parameters using a standardized API. A URLSearchParams instance is created by passing a query string to the constructor. The instance exposes various methods like get(), set(), and delete() to perform query string operations. These are demonstrated here:

let qs = "?q=javascript&num=10";

let searchParams = new URLSearchParams(qs);

alert(searchParams.toString()); // " q=javascript&num=10"
searchParams.has("num");        // true
searchParams.get("num");        // 10

searchParams.set("page", "3");
alert(searchParams.toString()); // " q=javascript&num=10&page=3"

searchParams.delete("q");
alert(searchParams.toString()); // " num=10&page=3"

Most browsers that support URLSearchParams also support using the URLSearchParams as an iterable object:

let qs = "?q=javascript&num=10";

let searchParams = new URLSearchParams(qs);

for (let param of searchParams) {
 console.log(param);
}
// ["q", "javascript"]
// ["num", "10"]

Manipulating the Location

The browser location can be changed in a number of ways using the location object. The first, and most common, way is to use the assign() method and pass in a URL, as in the following example:

location.assign("http://www.wrox.com");

This immediately starts the process of navigating to the new URL and makes an entry in the browser's history stack. If location.href or window.location is set to a URL, the assign() method is called with the value. For example, both of the following perform the same behavior as calling assign() explicitly:

window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";

Of these three approaches to changing the browser location, setting location.href is most often seen in code.

Changing various properties on the location object can also modify the currently loaded page. The hash, search, hostname, pathname, and port properties can be set with new values that alter the current URL, as in this example:

// assume starting at http://www.wrox.com/WileyCDA/
      
// changes URL to "http://www.wrox.com/WileyCDA/#section1"
location.hash = "#section1";
      
// changes URL to "http://www.wrox.com/WileyCDA/?q=javascript"
location.search = "?q=javascript";
      
// changes URL to "http://www.yahoo.com/WileyCDA/"
location.hostname = "www.yahoo.com";
      
// changes URL to "http://www.yahoo.com/mydir/"
location.pathname = "mydir";
      
// changes URL to "http://www.yahoo.com:8080/WileyCDA/
Location.port = 8080;

Each time a property on location is changed, with the exception of hash, the page reloads with the new URL.

When the URL is changed using one of the previously mentioned approaches, an entry is made in the browser's history stack so the user may click the Back button to navigate to the previous page. It is possible to disallow this behavior by using the replace() method. This method accepts a single argument, the URL to navigate to, but does not make an entry in the history stack. After calling replace(), the user cannot go back to the previous page. Consider this example:

<!DOCTYPE html>
<html>
<head>
 <title>You won't be able to get back here</title>
</head>
 <body>
 <p>Enjoy this page for a second, because you won't be coming back here.</p>
 <script>
  setTimeout(() => location.replace("http://www.wrox.com/"), 1000);
 </script>
</body>
</html>

If this page is loaded into a web browser, it will redirect to www.wrox.com after a second. At that point, the Back button will be disabled, and you won't be able to navigate back to this example page without typing in the complete URL again.

The last method of location is reload(), which reloads the currently displayed page. When reload() is called with no argument, the page is reloaded in the most efficient way possible, which is to say that the page may be reloaded from the browser cache if it hasn't changed since the last request. To force a reload from the server, pass in true as an argument like this:

location.reload();   // reload - possibly from cache
location.reload(true); // reload - go back to the server

Any code located after a reload() call may or may not be executed, depending on factors such as network latency and system resources. For this reason, it is best to have reload() as the last line of code.

THE NAVIGATOR OBJECT

Originally introduced in Netscape Navigator 2, the navigator object is the standard for browser identification on the client. The navigator object is common among all JavaScript-enabled web browsers. As with other BOM objects, each browser supports its own set of properties.

The navigator object implements methods and properties defined in the NavigatorID, NavigatorLanguage, NavigatorOnLine, NavigatorContentUtils, NavigatorStorage, NavigatorStorageUtils, NavigatorConcurrentHardware, NavigatorPlugins, and NavigatorUserMedia interfaces.

The following table lists each available property and method:

PROPERTY/METHOD DESCRIPTION
activeVrDisplays Returns an array of every VRDisplay instance with its ispresenting property set to true.
appCodeName Returns "Mozilla" even in non-Mozilla browsers.
appName Full browser name.
appVersion Browser version. Typically does not correspond to the actual browser version.
battery Returns a BatteryManager object to interact with the Battery Status API.
buildId Build number for the browser.
connection Returns a NetworkInformation object to interact with the Network Information API.
cookieEnabled Indicates if cookies are enabled.
credentials A CredentialsContainer to interact with the Credentials Management API.
deviceMemory The amount of device memory in gigabytes.
doNotTrack The user's do-not-track preference.
geolocation A Geolocation object to interact with the Geolocation API.
getVRDisplays() Returns an array of every VRDisplay instance available.
getUserMedia() Returns the stream associated with the available media device hardware.
hardwareConcurrency The device's number of processor cores.
javaEnabled Indicates if Java is enabled in the browser.
language The browser's primary language.
languages An array of all the browser's preferred languages.
locks A LockManager object to interact with the Web Locks API.
mediaCapabilities A MediaCapabilities object to interact with the Media Capabilities API.
mediaDevices The available media devices.
maxTouchPoints The maximum number of supported touch points for the device's touchscreen.
mimeTypes Array of MIME types registered with the browser.
onLine Indicates if the browser is connected to the Internet.
oscpu The operating system and/or CPU on which the browser is running.
permissions A Permissions object to interact with the Permissions API.
platform The system platform on which the browser is running.
plugins Array of plug-ins installed on the browser. In Internet Explorer only, this is an array of all <embed> elements on the page.
product The name of the product (typically "Gecko").
productSub Extra information about the product (typically Gecko version information).
registerProtocolHandler() Registers a website as a handler for a particular protocol.
requestMediaKeySystemAccess() Returns a Promise which resolves to a MediaKeySystemAccess object.
sendBeacon() Asynchronously transmits a small payload.
serviceWorker The ServiceWorkerContainer used to interact with ServiceWorker objects.
share() If available, invokes the current platform's native sharing mechanism.
storage Returns the StorageManager object to interact with the Storage API.
userAgent The user-agent string for the browser.
vendor The brand name of the browser.
vendorSub Extra information about the vendor.
vibrate() Triggers the device to vibrate if vibration is supported.
webdriver Indicates if the browser is controlled by automation.

The navigator object's properties are typically used to determine the type of browser that is running a web page.

Detecting Plug-ins

One of the most common detection procedures is to determine whether the browser has a particular plug-in installed. For browsers other than Internet Explorer 10 and below, this can be determined using the plugins array. Each item in the array contains the following properties:

  • name—The name of the plug-in
  • description—The description of the plug-in
  • filename—The filename for the plug-in
  • length—The number of MIME types handled by this plug-in

Typically, the name contains all of the information that's necessary to identify a plug-in, though this is not an exact science. Plug-in detection is done by looping over the available plug-ins and comparing a plug-in's name to a given name, as in this example:

// plugin detection - doesn't work in Internet Explorer 10 or below
let hasPlugin = function(name) {
 name = name.toLowerCase();
 for (let plugin of window.navigator.plugins){
  if (plugin.name.toLowerCase().indexOf(name)> -1){
   return true;
  }
 }
      
 return false;
}
      
// detect flash
alert(hasPlugin("Flash"));
      
// detect quicktime
alert(hasPlugin("QuickTime"));

The hasPlugin() example accepts a single argument: the name of a plug-in to detect. The first step is to convert that name to lowercase for easier comparison. Next, the plugins array is iterated over, and each name property is checked via indexOf() to see if the passed-in name appears somewhere in that string. This comparison is done in all lowercase to avoid casing errors. The argument should be as specific as possible to avoid confusion. Strings such as "Flash" and "QuickTime" are unique enough that there should be little confusion. This method works for detecting plug-ins in Firefox, Safari, Opera, and Chrome.

With the release of Internet Explorer 11, window.navigator supports plugins and mimeTypes. This means that the function defined previously is capable of detecting plug-ins properly for all up-to-date versions of browsers from major vendors. Furthermore, the ActiveXObject becomes hidden from the DOM in IE11, meaning it cannot be used for detection purposes.

Legacy Internet Explorer Plugin Detection

Detecting plug-ins in Internet Explorer 10 or below is more problematic because it doesn't support Netscape-style plug-ins. The only way to detect plug-ins in Internet Explorer is to use the proprietary ActiveXObject type and attempt to instantiate a particular plug-in. Plug-ins are implemented in Internet Explorer using COM objects, which are identified by unique strings. So to check for a particular plug-in, you must know its COM identifier. For instance, the identifier for Flash is "ShockwaveFlash.ShockwaveFlash". With this information, you can write a function to determine if the plug-in is installed in Internet Explorer as follows:

// plugin detection for legacy Internet Explorer
function hasIEPlugin(name) {
 try {
   new ActiveXObject(name);
   return true;
 } catch (ex) {
   return false;
 }
}
      
// detect flash
alert(hasIEPlugin("ShockwaveFlash.ShockwaveFlash"));
      
// detect quicktime
alert(hasIEPlugin("QuickTime.QuickTime"));

In this example, the hasIEPlugin() function accepts a COM identifier as its sole argument. In the function, an attempt is made to create a new ActiveXObject instance. This is encapsulated in a try-catch statement because an attempt to create an unknown COM object will throw an error. Therefore, if the attempt is successful, the function returns true. If there is an error, the catch block gets executed, which returns false. This code then checks to see if the Flash and QuickTime plug-ins are available in Internet Explorer.

Because these two plug-in–detection methods are so different, it's typical to create functions that test for specific plug-ins rather than use the generic methods described previously. Consider this example:

// detect flash for all browsers
function hasFlash() {
 var result = hasPlugin("Flash");
 if (!result){
  result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
 }
 return result;
}
      
// detect quicktime for all browsers
function hasQuickTime() {
 var result = hasPlugin("QuickTime");
 if (!result){
  result = hasIEPlugin("QuickTime.QuickTime");
 }
 return result;
}
      
// detect flash
alert(hasFlash());
      
// detect quicktime
alert(hasQuickTime());

This code defines two functions: hasFlash() and hasQuickTime(). Each function attempts to use the non–Internet Explorer plug-in–detection code first. If that method returns false (which it will for Internet Explorer), the Internet Explorer plug-in–detection method is called. If the Internet Explorer plug-in–detection method also returns false, then the result of the overall method is false. If either plug-in–detection function returns true, then the overall method returns true.

Registering Handlers

Modern browsers formally support the registerProtocolHandler() method to the navigator object. (This is formally defined in HTML 5.) These methods allow a website to indicate that it can handle specific types of information. With the rise of online RSS readers and online e-mail applications, this is a way for those applications to be used by default just as desktop applications are used.

A call can be made for protocols by using registerProtocolHandler(), which accepts three arguments: the protocol to handle (i.e., "mailto" or "ftp"), the URL of the page that handles the protocol, and the name of the application. For example, to register a web application as the default mail client, you can use the following:

navigator.registerProtocolHandler("mailto", 
 "http://www.somemailclient.com?cmd=%s", 
 "Some Mail Client");

In this example, a handler is registered for the mailto protocol, which will now point to a web-based e-mail client. Once again, the second argument is the URL that should handle the request, and %s represents the original request.

THE SCREEN OBJECT

The screen object (also a property of window) is one of the few JavaScript objects that have little to no programmatic use; it is used purely as an indication of client capabilities. This object provides information about the client's display outside the browser window, including information such as pixel width and height. Each browser provides different properties on the screen object. The following table describes each of the properties.

PROPERTY DESCRIPTION
availHeight The pixel height of the screen minus system elements such as Windows (read only).
availLeft The first pixel from the left that is not taken up by system elements (read only).
availTop The first pixel from the top that is not taken up by system elements (read only).
availWidth The pixel width of the screen minus system elements (read only).
colorDepth The number of bits used to represent colors; for most systems, 32 (read only).
height The pixel height of the screen.
left The pixel distance of the current screen's left side.
pixelDepth The bit depth of the screen (read only).
top The pixel distance of the current screen's top.
width The pixel width of the screen.
orientation Returns the screen orientation as specified in the Screen Orientation API.

THE HISTORY OBJECT

The history object represents the user's navigation history since the given window was first used. Because history is a property of window, each browser window object has its own history object relating specifically to that instance. For security reasons, it's not possible to determine the URLs that the user has visited. It is possible, however, to navigate backwards and forwards through the list of places the user has been without knowing the exact URL.

Navigation

The go() method navigates through the user's history in either direction, backward or forward. This method accepts a single argument, which is an integer representing the number of pages to go backward or forward. A negative number moves backward in history (similar to clicking the browser's Back button), and a positive number moves forward (similar to clicking the browser's Forward button). Here's an example:

// go back one page
history.go(-1);
      
// go forward one page
history.go(1);
      
// go forward two pages
history.go(2);

The go() method argument can also be a string, in which case the browser navigates to the first location in history that contains the given string. The closest location may be either backward or forward. If there's no entry in history matching the string, then the method does nothing, as in this example:

// go to nearest wrox.com page
history.go("wrox.com");
      
// go to nearest nczonline.net page
history.go("nczonline.net");

Two shortcut methods, back() and forward(), may be used in place of go(). As you might expect, these mimic the browser Back and Forward buttons as follows:

// go back one page
history.back();
      
// go forward one page
history.forward();

The history object also has a property, length, which indicates how many items are in the history stack. This property reflects all items in the history stack, both those going backward and those going forward. For the first page loaded into a window or tab, history.length is equal to 0. By testing for this value as shown here, it's possible to determine if the user's start point was your page:

if (history.length == 0){
 //this is the first page in the user's window
}

The history object typically is used to create custom Back and Forward buttons and to determine if the page is the first in the user's history.

History State Management

One of the most difficult aspects of modern web application programming is history management. Gone are the days where every action takes a user to a completely new page, which also means that the Back and Forward buttons have been taken away from users as a familiar way to say “get me to a different state.” The first step to solving that problem was the hashchange event (discussed in the Events chapter). HTML5 updates the history object to provide easy state management.

Where the hashchange event simply let you know when the URL hash changed and expected you to act accordingly, the state management API actually lets you change the browser URL without loading a new page. To do so, use the history.pushState() method. This method accepts three arguments: a data object, the title of the new state, and an optional relative URL. For example:

let stateObject = {foo:"bar"};

history.pushState(stateObject, "My title", "baz.html");

As soon as pushState() executes, the state information is pushed onto the history stack and the browser's address bar changes to reflect the new relative URL. Despite this change, the browser does not make a request to the server, even though querying location.href will return exactly what's in the address bar. The second argument isn't currently used by any implementations and so it is safe to either leave it as an empty string or provide a short title. The first argument should contain all of the information necessary to correctly initialize this page state when necessary. To prevent abuse, the size of the state object is limited, typically to less than 500MB–1MB.

Since pushState() creates a new history entry, you'll notice that the Back button is enabled. When the Back button is pressed, the popstate event fires on the window object. The event object for popstate has a property called state, which contains the object that was passed into pushState() as the first argument:

window.addEventListener("popstate", (event) => {
 let state = event.state;
 if (state) {  // state is null when at first page load
  processState(state);
 }
});

Using this state, you must then reset the page into the state represented by the data in the state object (as the browser doesn't do this automatically for you). Keep in mind that when a page is first loaded, there is no state, so hitting the Back button until you get to the original page state will result in event.state being null.

You can access the current state object by using history.state. You can also update the current state information by using replaceState() and passing in the same first two arguments as pushState(). Doing so does not create a new entry in history, it just overwrites the current state:

history.replaceState({newFoo: "newBar"}, "New title");

The state object passed into pushState() or replaceState() should only contain information that can be serialized. Therefore, things like DOM elements are inappropriate for use in the state object.

SUMMARY

The Browser Object Model (BOM) is based on the window object, which represents the browser window and the viewable page area. The window object doubles as the ECMAScript Global object, so all global variables and functions become properties on it, and all native constructors and functions exist on it initially. This chapter discussed the following elements of the BOM:

  • To reference other window objects, there are several window pointers.
  • The location object allows programmatic access to the browser's navigation system. By setting properties, it's possible to change the browser's URL piece by piece or altogether.
  • The replace() method allows for navigating to a new URL and replacing the currently displayed page in the browser's history.
  • The navigator object provides information about the browser. The type of information provided depends largely on the browser being used, though some common properties, such as userAgent, are available in all browsers.

Two other objects available in the BOM perform very limited functions. The screen object provides information about the client display. This information is typically used in metrics gathering for websites. The history object offers a limited peek into the browser's history stack, allowing developers to determine how many sites are in the history stack and giving them the ability to go back or forward to any page in the history, as well as modify the history stack.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.79.60