Chapter 7. JavaScript in a Window

In the previous chapters, we explored JavaScript as ECMAScript, a powerful object-oriented language, and we saw how MooTools uses the native features of the language to enhance it by adding features like a class system and a better type system.

But while the JavaScript language itself is a very interesting topic, the application of JavaScript for practical use is important as well. From its inception, JavaScript has always been a practical language, specifically for creating applications that run on a web browser. Of course, this is no longer the case. JavaScript has ceased to be a browser-bound language and has moved into the realm of the server, as we'll see in the last part of this book. However, we must first focus on JavaScript as it was envisioned: a language for building browser-based programs.

This chapter begins our journey through browser-based JavaScript. First, we'll learn a little about the browser itself, and then we'll take a look at the APIs the browser provides for working with web documents. Finally, we'll discuss the issue of cross-browser development, and how MooTools makes working with multiple-browsers possible.

A Language for Every Computer

JavaScript can be considered the most popular scripting language in the world. Most personal computers today, from big desktop machines to tiny handhelds and smartphones, have full JavaScript interpreters installed on them—usually as part of the default software package. And JavaScript owes this ubiquity to being the default scripting language of the humble web browser.

Well, maybe not so humble anymore. In the early days of the Internet, web browsers were very simple applications for displaying content. Web pages back then were simple: some text, a few images, and not much graphical style. Basic stuff compared to what we regularly see online these days.

But things got a little more complex when Netscape introduced JavaScript in 1996, which led to the new idea of the scripted browser. Suddenly, simple web pages became much more interactive, and the browser became far more than an application to display web pages. And it was this ability to add dynamic behavior to web sites that gradually evolved over the years into the complex JavaScript ecosystem we have today.

These days, browsers are much more powerful, more efficient, and more flexible. The competition among browser developers to create the best web browser can be brutal at times, but it does have the benefit of bringing about innovations in web technologies. And for a programming language like JavaScript, whose fate is irreversibly tied to the web browser, such innovations are beneficial to its future.

But what role does the browser really play in the JavaScript ecosystem? To put it simply, the browser is the platform for developing JavaScript applications: our programs are not only built around it, our users also access our programs via the browser. And while the browser is no longer the only JavaScript platform (more on this in Part Three), its availability and pervasiveness in modern computing is enough to warrant our full attention.

The subject of browsers is quite complex—after all, it's both an application and a platform. It's not possible to really get into a deep discussion on the internal architecture of a browser in this chapter, especially since there's no "one true browser," so instead we'll focus on the browser as part of the JavaScript ecosystem, particularly on the additional APIs it provides for our use. But before any of that, we need to understand a very basic subject that seems to be glossed over by most JavaScript books: how does a browser actually display a page?

Life Cycle of a Page

At its core, the web browser is an application that displays web pages. The main component of a web page is the HTML markup that describes the nature and content of the page and defines the external resources that are associated with the page, like style sheets and images. It is the job of the browser to turn this markup from text-based code into a graphical representation that will be displayed to the user.

Each browser uses different methods to achieve this, and how one browser does it may or may not be the same as how others do it. Still, there is a general flow that's applicable to all browsers, as Figure 7.1 illustrates.

The first thing a browser does is download the particular page for processing. When we enter a Uniform Resource Identifier (or URI) in a browser, the browser's fetch engine uses this identifier to locate and download the page and store it in its resource cache, where downloaded resources are kept for a specific amount of time to avoid having to download them each time they're accessed. In fact, if the page or resource we're accessing is already in the cache, the fetch engine often skips the downloading process, and uses this cached version instead.

When the fetch engine finishes downloading the page, the browser does a quick parsing of the markup to detect external resources—things like images, CSS stylesheets, or embedded media. The fetch engine then downloads these external resources while the browser starts the next step in the process.

As the fetch engine downloads the resources, the browser starts parsing the HTML markup of the page to build a DOM Tree. We'll discuss this in more depth later and in the next chapter, but for now it's enough to say that this DOM Tree is the HTML markup transformed into a hierarchical, tree-structured object. At this point, the text-based HTML markup becomes a usable object that can be manipulated by the browser.

The browser then starts parsing the CSS style sheets and the style definitions associated with the page to create a style object. This style object is used to determine how the browser will display the page, and it consolidates the final styles that will be applied to items in the DOM Tree based on the rules of CSS.

When both the DOM Tree and the style object have been prepared, the browser builds the render tree by applying the rules from the style object to the DOM Tree. The resulting render tree is sometimes different from the DOM tree: since some items won't be displayed because of style rules, they won't be present in the render tree and won't be processed in the next step.

Now the browser proceeds to do what is called a reflow, a process by which the browser's layout engine calculates the size and position of individual items in the render tree in relation to the whole page. By the time this process is done, all items in the page are properly positioned, and the final layout of the page is finished.

The last step in displaying the page is handing over the work to the browser's paint engine, which renders or "paints" the graphical representation of the page by combining the data from the render tree with the calculations done by the reflow. The page is then displayed by the browser in its window, and the user will be able to view it.

However, that's not the end of the story. After the page has been rendered, the browser will go into an event loop—and this is when things start getting interesting. Basically, the browser waits for something to happen, and that something is loosely termed an "event." An event can be anything from a user clicking a link or resizing the browser window to scripts performing manipulations on the page. When the browser detects an event that affects the layout of the page, it goes through the reflow and painting steps once again in order to update the displayed page.

A typical browser flow

Figure 7.1. A typical browser flow

This "reflow, paint, wait for event" process is repeated by the browser for as long as necessary. In fact, this looping process plays a central role in visual animation as well as in browser events, which we will talk about in Chapters 12 and 9 respectively.

Pause, Script

One element that has to be taken into account in this rendering process is <script> tags. We know that <script> tags are the containers that enable us to program a web page, and whatever we put inside these tags is executed by the JavaScript interpreter that's built into the browser. This execution complicates the rendering process.

JavaScript is a single-threaded language—there can be only one process working at a time. Take a look at this example:

function B(){
    console.log('B'),
};

console.log('A'), // 'A'
B(); // 'B'
console.log('C'), // 'C'

The output of this snippet is 'A', 'B' and 'C', in that order. After we logged the first string 'A', we called the function B() and it logs the second string 'B'. The execution is sequential: the JavaScript interpreter waits for the function to finish before executing the next line.

This single-threaded nature also affects the rendering process. When the browser's parsing engine encounters a <script> tag, it halts its parsing to let the JavaScript interpreter execute the contents of the <script> first. It is only after the interpreter finishes executing the script that the parsing (and rendering) process continues. In the case of scripts that have a src attribute, the browser first fetches the external script and then executes the contents of the script before continuing—which could make your web pages unresponsive for a time.

There are two reasons for this behavior. The first is because of the scripted nature of the browser: JavaScript, through browser-exposed APIs, can manipulate items on the page, which leads to a difference between the original content of the page and the new, modified version. Since JavaScript can affect the page's layout, the browser needs to make sure that all these layout-modifying processes are executed before rendering the page so that the final layout that will be displayed remains consistent with the modification.

The second reason is so that all scripts in the page are executed sequentially. Because all scripts on a web page share the same global environment, it is common for programs to be divided across multiple scripts, which then share data through the global environment. If scripts weren't executed sequentially, a script that depends on some variables being set by a script declared prior to it might not get access to these variables. Scripts are therefore executed according to the order they appear on the page to make sure any dependencies are properly resolved.

This blocking process and sequential execution, though, presents a very distinctive problem in terms of DOM manipulation. We'll look at it in detail in the next chapter. Right now, we'll focus our attention on the APIs that intersect the browser and JavaScript.

The Scripted Browser

If we are being truly technical, the things we've discussed in the previous chapters were about ECMAScript—not JavaScript. In fact, almost all of the snippets we presented could be executed by any compliant ECMAScript 3 interpreter—whether or not that interpreter is a JavaScript interpreter.

JavaScript is really a different beast from ECMAScript, though. While JavaScript is based on ECMAScript, it adds other host objects to the language that make it possible to execute complex programs in the browser. It is these additional APIs that turn ECMAScript into JavaScript, and enable the language to transform the browser from an application for displaying static web pages into a fully scriptable application platform.

Unfortunately, things are not really that simple: there is no definitive specification of what actually constitutes JavaScript, or what makes it different from ECMAScript—except for the idea that JavaScript has additional features. What these additional features are exactly, we can't precisely say. To quote Allen Wirfs-Brock, implementer of Tektronix Smalltalk and current JavaScript Language Architect for Microsoft:

In an ideal world, a standards specification is the complete story. A JavaScript implementation would just include everything in the ECMAScript specification and nothing more. [..] In the real world of the web, things are not so clear-cut. Specifications are seldom perfect and sometimes they are intentionally incomplete or ambiguous. [..] The result is that there are widely implemented and used features that are not defined by any standard specification.

The problem is rooted in how JavaScript—and ECMAScript—came to be. After Netscape introduced JavaScript and the idea of the scripted browser in 1995, Microsoft decided to implement the same features for its Internet Explorer browser. To do this, Microsoft reverse-engineered JavaScript to create JScript, a seemingly compatible language implementation. Standardization of the language into ECMAScript came later, after both Netscape and Microsoft had already shipped their own implementations.

Standardization didn't help in defining all of JavaScript either. ECMAScript was created to standardize the core of the language itself, in terms of language syntax, semantics, and execution. But the true core of JavaScript during those times—the API that enabled developers to use the language for web pages—was left out of the specification, along with the all the additional features each browser maker implemented to attract more developers to use their respective browsers.

And as the years passed, more features were added by browser developers that were not in the specification. Some of these innovations were later absorbed into the official language, some became consensus features that were adopted by all browsers even though they were not in the specs, and the rest became browser-specific features that are incompatible or missing from other implementations. The result is a rich and powerful language with a long list of features that may or may not work in all implementations—and more unfortunate, a language whose entire scope we can never define.

Thankfully, all is not lost. We may never really know what JavaScript is, but we don't have to in order to use it. The core language—ECMAScript—is well-defined, and the most important APIs for browser-based development have been standardized, or at least considered "stable enough" consensus features to guarantee they'll work in all browsers. And the two most important APIs, the Browser Object Model and the Document Object Model, are our next topics.

The Document Object Model

The Document Object Model, or DOM, is a set of technologies that define the API for interacting with and manipulating documents—particularly XML documents and its cognate variants. At its center is the idea of the document as an object composed of smaller interconnected objects, which can be manipulated to control the structure of the document itself. While the DOM specification isn't bound to one programming language, the DOM and JavaScript have become almost synonymous with client-side scripting.

Like JavaScript and other web technologies, the DOM came into existence long before it became a specification. When Netscape released its first JavaScript-enabled version of the Netscape Navigator browser, it included a very basic API for manipulating the page consisting of the global document object and array-like properties referencing document elements, such as document.forms, document.images, and document.anchors. Microsoft, in creating its JavaScript-compatible JScript, implemented the same API in order to make its Internet Explorer browser compatible with web pages developed in Netscape Navigator.

Those early APIs form the first cross-browser implementation of a DOM-like ancestor, which we now call DOM Level 0. This "level 0" designation indicates that these APIs are non-standard, but they formed the basis of further standardization efforts. Much of DOM Level 0 is still implemented in most browsers, and some of the parts that were not standardized became important consensus features as we'll see later.

After their initial scurry to release the first versions of JavaScript and JScript, Netscape and Microsoft eventually diverged by adding two new, different DOM implementations. Netscape developed a model that revolved around layers, which are special scriptable elements representing individual documents. Microsoft, on the other hand, developed a special object called document.all, which is an array-like object representing all elements in the document.

These two models are called the Intermediate DOMs and they gave web developers more powerful document-manipulation features, at the expense of cross-browser compatibility. Developers then had to either target individual browsers or create two versions of the code to support both. Suddenly, DOM scripting became much more cumbersome and complicated, and it is perhaps this complexity that fostered the initial animosity towards the JavaScript language—even though the DOM itself was technically separate from JavaScript.

Fortunately, the standardization of the JavaScript language as ECMAScript triggered the eventual standardization of the DOM. The World Wide Web Consortium (W3C) spearheaded the effort in 1997, and the first specification for the DOM, named DOM Level 1, was released four years later. The DOM Level 1 specification was initially targeted toward XML documents, but because XML and HTML are cognate languages, the DOM model was deemed appropriate for HTML use as well. This specification came in two parts: Core and HTML. The Core specification detailed the structure and API of the document as an object composed of other objects, while the HTML specification added features specifically for HTML documents.

Eventually, Netscape and Microsoft adopted this new standard, which brought cross-browser compatibility back for both browsers. When Netscape passed its development torch to the Mozilla foundation, Mozilla decided to drop the previous layer-model from Netscape's intermediate DOM and instead focused on DOM Level 1 exclusively for its Firefox browser. Meanwhile, Microsoft—for the sole purpose of backward compatibility—still includes legacy DOM support in Internet Explorer.

The next DOM specification to arrive was DOM Level 2, which brought new DOM features. This specification is composed of six parts, the two most important of which are the DOM Events and the DOM Styles specifications. DOM Events added a new event model to the DOM—a very important feature that we'll discuss in length in Chapter 9, while DOM Styles added further CSS and style sheet capabilities to the model, which are important for visual animations, as we'll see in Chapter 11. After DOM Level 2 came DOM Level 3, which builds on DOM Level 2 and adds new features to the Core and Events specification, as well as new specifications such as XML loading and saving and document validation.

One thing to bear in mind is that even if the DOM specifications are considered standards, browser support for these specifications isn't as well-defined as you might think. DOM Level 1 is highly supported in most browsers, but DOM Levels 2 and 3 are a different matter. Browsers generally implement only subsets of these specifications, which means that not all features are available in all browsers. This creates problems when using these APIs for cross-browser scripting.

Thankfully, the subset of important APIs—such as the ones dealing with document manipulation—is available in all browsers. This doesn't mean that they're perfect, of course. In fact, a lot of these APIs have implementation-specific bugs that make the DOM a very hard thing to work with—and these issues are the reason frameworks like MooTools exist. Frameworks were specifically built to make working with multiple browsers easier by abstracting the native APIs to implement cross-browser solutions. MooTools uses several techniques to do this, which we'll discuss later in this chapter.

Because the DOM itself is a complex set of APIs and interrelated ideas, we can't cover all of it in this chapter. Therefore, we'll divide our discussion across several chapters, starting with the core of the DOM model in the next chapter, then events and styles, and ending with some specific DOM manipulation tasks connected to the whole "Ajax" movement. Right now, though, we'll focus on an important part of the legacy DOM.

The Browser Object Model

The Browser Object Model, or BOM, is a set of related APIs that deal with the browser itself. At its heart is the idea of representing the browser window as a JavaScript object. The BOM API is represented by the top-level window object, which acts not only as the main interface for accessing the BOM but also as the representation of the actual browser window and as the global JavaScript object in the browser.

Technically, the BOM is part of the DOM, specifically the Level 0 DOM, and it's one of the consensus features in browser-based JavaScript: there is no specification that describes the BOM, which means that it's pretty much implementation-dependent. However, most browsers do implement a "standard" set of useful objects for the BOM:

  • navigator—represents the browser application itself.

  • location—represents the current resource being displayed.

  • history—represents the browser's history timeline.

  • document—represents the current HTML page.

All of these objects are properties of the window object and, since the window object also serves as the global object in the browser, these objects are also available as top-level objects in JavaScript. There are some other BOM APIs, like screen or frames, which we won't cover here since they aren't relevant to our current discussion.

Since the BOM itself is not that complex an object, we'll simply go through these important members in the next sections. One exception is the document object, which we'll discuss in the next chapter because of its importance in the DOM Element model. And we'll cover the actual use of these objects in practice later on as we go through this chapter and the next ones, so we'll limit our discussion here to the basics.

Note

Some parts of the BOM might not retain their status as consensus features for long. HTML5, the newest version of the HTML language currently under development, includes specifications for most parts of the BOM, like window, location, and history. This means that future browsers will no longer have to depend on an API based on what we loosely describe as DOM Level 0, but on a better specification.

The navigator Object

The name of the navigator object is of course a reference to the first scripted browser, Netscape Navigator. When Microsoft reverse-engineered the original JavaScript implementation to create its own JScript engine for Internet Explorer, the name of the object was retained—probably for the purpose of cross-platform compatibility. Because the BOM is not part of the ECMAScript standard, the object identifier navigator was never replaced, and all major browsers still support this BOM object.

The navigator object acts as the representation of the current browser, and the properties of this object are used to describe the browser. In an ideal world, the navigator object would be the prime candidate for browser detection: we'd simply detect the browser by examining the properties of the navigator object, and then work around the various bugs in the browser using platform-targeted code. Of course, we live in a far from ideal world, and the navigator object and its properties are almost useless for browser detection.

Early developers saw the same potential for the navigator object as a useful way to perform browser detection. Unfortunately, those early days of browser development were truly competitive: not only were browser vendors trying to lure more developers to their products by adding new features, they were also trying to win market share by ensuring that web pages developed in other browsers would work with theirs. Specifically, early browser vendors made sure that their implementations appeared as if they were Netscape Navigator (the leading browser at the time), and a big part of this involved spoofing the properties of the navigator object.

This practice hasn't died out, and we still see browsers with navigator.appName properties with the value 'Netscape', even though Netscape Navigator is no longer widely used. Thus, a lot of the properties of the navigator object are useless for the purpose of browser detection, and we won't talk about the navigator object in detail for the same reason.

One property, though, navigator.userAgent, is still important and is still used for browser detection, despite traces of legacy spoofing being present in its value. We'll talk about this later in the section on browser detection.

The location Object

The location object represents the current location of the browser. In particular, it represents the current page from which the specific JavaScript code is executed.

The most important property of the location object is href, a string that denotes the current URL of the page. If we access a page with the URL http://www.foobaz.com:80/_files/test.html?name=mark#index, for example, that entire URL will be the location.href value. The href property therefore denotes the full URL of the current page and is useful for getting the location of the currently running script.

Aside from the href property, the location object also has properties that refer to each part of the URL:

  • protocol represents the particular protocol used to access the page, usually with the value http: or https:. Note that the value of this property also includes the colon, not just the protocol name. In our example above, the value of location.protocol is 'http:'.

  • hostname represents the host section of the page's URL, including any subdomains. In our example, location.host has the value 'www.foobaz.com'.

  • port represents the port section of the URL as a string—if available. If no port is specified in the URL, location.port will be equal to an empty string. In our example URL, the value of this property is '80'.

  • host is a shortcut property that combines both the hostname and port values of the page's URL with proper delimiters. The value of location.host in our URL, for example, is 'www.foobaz.com:80'.

  • pathname represents the specific resource location as defined by the URL. The value of this property always starts with /, and for URLs with no specific paths, the value will only be '/' to denote the root resource. In our example URL, location.pathname is equal to '/_files/test.html'.

  • search represents the query string portion of the URL. The query string is a string that's used to pass data to the server. Usually it contains a collection of key-value pairs in the format key=value, with each pair delimited by an ampersand (&). The whole string—as represented by location.search—follows the same URL format by being prefixed with a question mark. The value of location.search for our example is '?name=mark'.

  • hash represents the anchor part of the URL, and its value is an empty string if not present in the URL. Like the search property, hash also follows the URL format by being prefixed with a hash sign, #. In our example, location.hash is '#index'.

As you've probably noticed, the href property combines all of these properties together to produce the final URL. You can get the same value as location.href by concatenating the other properties with the proper delimiters:

var url = [
    location.protocol,
    '//',
    location.host,
    location.pathname,
    location.search,
    location.hash
].join(''),

console.log(url === location.href); // true

The location object also has methods for working with the browser's location. The three most important ones are reload, assign, and replace.

  • The reload method reloads the current page and is equivalent to refreshing the page using the browser. It takes a single optional boolean argument, force, which determines whether to bypass the browser's cache and reload the page directly from the server.

  • The assign method takes a single argument, url, and loads that URL in the current window. Take note that the url value is interpreted as a relative URL unless you specify a protocol. For example, if we are on http://foobaz.com/ and we do location.assign('mootools.net'), the URL http://foobaz.com/mootools.net will be loaded. However, if we do location.assign('http://mootools.net') instead, the proper location will be loaded in the browser.

  • The replace method works just like assign with one big difference: it does not register the URL in the browser's history. If we do location.assign('http://mootools.net'), for example, a new entry in the browser's history will be created and the user can go back to the previous page using the back button. However, if we do location.replace('http://mootools.net'), no new entry is created in the history. Instead, the current entry in the history will be modified to point to the loaded location.

The reload and assign methods are the two most-used methods of the location object, although replace can be handy in some cases.

One nifty feature of the location object is related to the assign method: the properties of the location object are dynamically tied to the actual location, and changing the value of a property changes the location of the page as well. For example, setting location.href = 'http://mootools.net' will load the URL in the current window—the same behavior we get with location.assign('http://mootools.net').

The really interesting use of this feature, though, is when we work with the properties of location that relate to parts of the URL. When we assign a new value to a property that references only a section of the URL, it changes only that section of the URL. Going back to our original URL example, http://www.foobaz.com:80/_files/test.html?name=mark#index, if we do location.hostname = 'mootools.net', it loads http://mootools.net:80/_files/test.html?name=mark#index in the current window. As you can see, only the original hostname is changed—from www.foobaz.com to mootools.net—and all the other parts of the URL remain the same. The same thing goes for all other sectional properties, such as search or port.

One really important use of this feature is with history management via location.hash. Unlike other URL parts, changing the anchor of a URL doesn't reload the page. Together with the history object, this makes it possible to implement some kind of history management for Ajax applications, as we'll see in Chapter 11.

The history Object

The most basic model of the web is of documents connected through hyperlinks. Users go to one document and navigate by clicking a link in the document that points to another document. Because of this linked nature, browsers had to find a way to present a linear progression of changes from one location to another, as well to provide a way to help users navigate through this linear progression. Thus, the concept of browser history was born.

The history object represents the linear history of the current browser window. In earlier browsers, the actual locations that were visited were available through the history object as URL strings. This, however, presented a large privacy hole in the browser, and was thus scrapped.

The history object today is a very simple object containing one property, length, which represents the number of items in the current window's history stack. It also has two simple methods, back and forward, which respectively load the previous and next locations in the browser history relative to the current location.

Another method, go, takes a numeric argument and loads the particular location relative to the current location, which is given the index 0. For instance, history.go(-1) will load the previous page in the history, which therefore makes it similar to doing history.back(), while history.go(2) will load the page after the next page in the history, just like hitting the browser's forward button twice.

Note

The new HTML5 specification adds two new methods to the history object: pushState and replaceState. These are considered history-modifying features, and they are important features that can be used to manage history states for Ajax applications. We'll talk more about them in Chapter 11.

Frameworks, Libraries, and Toolkits

If you recall Mr. Wirfs-Brock's words earlier in this chapter, you'll notice they refer in particular to specifications and how the lack of standards for a lot of browser features make it hard for browser developers to create truly compatible products. However, specifications only refer to one side of the story. If we happen to suddenly find ourselves in an ideal world where all features are standardized with proper specifications, we still have to grapple with one big problem: implementation inconsistencies. Or to put it more succinctly, bugs.

We could have perfect standards, and for the most part we have a very good set of them in our less-than-ideal world, but we can't escape the fact that the software industry is one that makes mistakes. We have clear, solid specifications for ECMAScript, the DOM, and the HTML language, yet our browsers are far from perfect in their implementations. Standards are therefore only part of the equation: getting browsers to properly implement these specifications is just as important.

And this is a real problem we face in cross-browser development. Not only are we dealing with the fact that not all features are available in all browsers, we also have to work around the various bugs in the features that are present. Fortunately, this problem isn't crippling and most inconsistencies and bugs can be solved using JavaScript itself. This brings us to the topic of frameworks, libraries, and toolkits.

Most of the popular JavaScript code collections these days would call themselves a library, a framework, or a toolkit. These terms have very different meaning in software development, so for the purpose of our discussion, we'll use the most generic term—library—to denote all of them. All current JavaScript libraries—despite how they may categorize themselves—were built with at least one of these three goals in mind:

  • Make cross-browser DOM development easier by providing abstractions that work around the inconsistencies and incompatibilities of the browser.

  • Make cross-browser JavaScript development better by fixing inconsistencies among different implementations.

  • Add additional features that make working with both the DOM and JavaScript easier.

The first goal is perhaps the most important one, because JavaScript's main stage has always been the browser. All major JavaScript libraries, including MooTools, will have some facility that makes writing cross-browser DOM scripts easier using simpler APIs. These APIs include stuff for element creation and manipulation, style management, an events system, and animation systems.

The last two goals are sometimes seen as much less important, depending on the library. While most JavaScript libraries add features that make working with the DOM easier, not all of them are concerned with adding new features to the JavaScript language. We do know that's not the case with MooTools: the framework implements several non-DOM related features, such as the class system and the type system, which makes MooTools a true JavaScript framework and not just a DOM scripting library. But it is important to note that not all libraries give the same amount of focus to the language as MooTools.

We've already examined how MooTools adds new language-level features to JavaScript in the previous chapter on Class and Type, and we'll examine the specifics of MooTools' DOM-related features in the next chapters.

MooTools and the Browser

As I mentioned, a big problem with cross-browser development is the fact that browsers are often inconsistent. While we do have full specifications for ECMAScript and the DOM, it doesn't necessarily follow that the browser implementations follow these specifications properly. Whether the inconsistencies are the consequences of bugs or a vague misinterpretation of the spec is of no big concern for us; what matters is that the cross-browser development is often a buggy affair.

And because browser vendors are largely concerned with making their own products better rather than maintaining compatibility with their competitors, the burden of making cross-browser development easier therefore gets passed to frameworks and libraries like MooTools.

Fixing Browsers with MooTools

At a general level, the MooTools approach is based on the idea of abstraction versus fabrication. What this means is that instead of creating an entirely new system to replace the native one, MooTools creates a new system by providing a new API that abstracts the native system. We've already seen this approach at work in the class and the type systems, and we'll see the same philosophy applied to DOM-related APIs as well.

This approach provides a big benefit when it comes to dealing with browser-specific issues. Because the native system is abstracted, MooTools is able to apply fixes internally, without the user's knowledge. This makes it possible to apply additional fixes in the future without changing APIs. It also makes it possible to apply fixes in a fine-grained manner, targeting only specific items without affecting the whole system.

MooTools has two main ways of dealing with implementation fixes. The first is test-based fixes, and this technique is easier to implement in most cases. Basically, MooTools tests for inconsistencies in the current browser by running a piece of code and then deciding whether or not to apply a fix based on the results of the test.

One example is Array.prototype.splice. In certain versions of Firefox and Internet Explorer, the splice method does not work properly with array-like objects. As we found out in Chapter 6 when we discussed Type, array methods should be generic according to the specs and they should work not only on real arrays but on array-like objects, too. Unfortunately, this is not the case with some versions of the aforementioned browsers, which becomes a problem for MooTools' Elements type that relies on this method.

To overcome this issue, MooTools uses a simple test-based fix. First, it performs a basic operation on an array-like object using splice, and then it checks the result. If the result is correct, MooTools does nothing. However, if the result is incorrect, MooTools implements its own version of splice for the Elements type that works properly. The result is a proper splice method for Elements regardless of browser. And because the fix is applied on an as-needed basis, only browsers with improper implementations are targeted, and browsers that are already conformant are left alone.

But while test-based fixes are handy, not all issues can be resolved using this method. The limitation of this technique is imposed by how the technique itself works: to successfully apply the fix, you must be able to run a test that detects the need for a fix. Unfortunately, some issues aren't testable, like memory and rendering issues. These problems can be detected and observed with the proper external tools, but they can't be tested using JavaScript alone due to the lack of proper APIs.

Therefore, another technique must be used. These issues, more often than not, are browser-specific, which means that only a single browser is affected. This therefore limits the scope of the fix: if we know that the current browser is buggy, we can apply the fix. Finding out whether there are problems with the current browser and what code needs to be applied to fix them is a whole other topic, but applying the fix itself is easy—all we have to do is detect the current browser and run the fix-related code as necessary.

MooTools provides a single global object, Browser, that forms the center of MooTools' browser-related APIs. Some of its properties like Document, Element, and Event, are actually references to the native browser objects of the same identifiers, which are used further on for MooTools' reimplementation of the element and event systems. The Browser object itself, though, has a much more important use: it's the basis for browser-specific decisions in the MooTools framework.

The Browser object has two main properties that relate to the browser itself: name, which is the string name of the current browser, and version, which denotes the version of the current browser as a number. For example, if MooTools is run on a page in Google Chrome 7, Browser.name will be 'chrome' and Browser.version will be 7.

Additionally, the Browser object has dynamic properties that act as shortcuts for these two properties. The first one is the dynamic name property, which is added via Browser[Browser.name] = true;. In Chrome, for example, Browser.chrome will be equal to true and Browser.ie will be undefined. On the other hand, Browser.ie will be true in Internet Explorer, while Browser.chrome will be undefined. The other dynamic property is the dynamic name-version property, which combines the name and version of browser, such as Browser.chrome7 or Browser.ie8.

Note

The dynamic name-version property has a limitation that makes the property name only register major versions. For example, Firefox 3.5 will register Browser.firefox3, and not Browser.firefox3.5, because the period—which is used in version numbers as a delimiter—is used for property access in JavaScript.

These Browser properties are used throughout MooTools, and they're essential to fixing browser-specific issues. Suppose we find an issue with Apple Safari that affects version 5 of the browser, and we need to apply some code to fix the issue. Using the Browser object, we simply have to do something like this:

if (Browser.safari5){
    // code
}

If we run this code on Safari 5, the code inside the if block will be executed, thereby targeting the browser properly. However, other browsers will ignore this code because Browser.safari5 will be undefined in them and the if condition will fail.

Browser Detection

The Browser object raises an interesting question: how does MooTools actually know what browser is currently running? This brings us to the topic of browser detection.

There are two main techniques for browser detection and the first is called user agent sniffing. User agent is another term for client software in a client-server system, such as a browser. In the web architecture, user agents identify themselves to servers using a special string called the user agent string, which follows the format <Product Name>/<Product Version> <Comments>.

The current browser's user agent string is accessible via the navigator object's userAgent property, and user agent sniffing relies on this property for detecting the current browser. The core of the operation involves running some regexp-based tests on the userAgent property value, then extracting parts of the string for use.

The user agent string is easily parsable, even if the structure of user agent strings among browsers varies widely. The problem with this technique, though, is that user agent strings are easily changed, and therefore can't be relied on. This is especially problematic when user agent spoofing is involved, where user agents "pretend" to be other user agents by passing the user agent string of another user agent to the server.

A second technique is much less prone to spoofing, and it's called feature sniffing. This technique involves checking for the existence of several special features in the current environment: if the environment has the both features X and feature Y, then it's probably browser Z. Feature sniffing therefore relies on the existence of certain objects to perform inference, and is therefore related to the concept of duck typing as we saw in the previous chapter.

But while feature sniffing is much more reliable than user agent sniffing, it's more brittle. Because we're checking for the existence of certain objects, adding or removing these objects from the current environment will result in wrong inferences. It's even more problematic when we take into account the pace of browser development: new versions of browsers are released more often these days, and a feature we use for feature sniffing could be gone instantly.

In versions of MooTools prior to 1.3, feature sniffing was the choice for browser detection. For example, MooTools relied on the existence of window.opera to check if the current browser is Opera, while it used window.ActiveXObject to check for Internet Explorer. This worked well for the framework—until the release of Firefox 3.6, which removed the getBoxObjectFor method of the document object, thereby breaking the MooTools feature-sniffing tests. This affected all versions of MooTools, making it necessary to call for an upgrade for all MooTools users.

Because of the magnitude of this incident, it was decided to move away from feature sniffing to user agent parsing for future versions of the framework, which of course included MooTools 1.3. In preparing the name and version properties of the Browser object, MooTools first parses navigator.userAgent using a regular expression, and then uses this parsed version to fill up the values of the properties. The properties are then used to create the dynamic name and name-version properties.

Feature Detection

But because user agent sniffing is easily breakable using spoofing, MooTools does not rely entirely on the name and version properties of the Browser object. As I mentioned earlier, test-based fixes are normal in MooTools and are used as much as possible. Another technique, related to both test-based fixes and feature sniffing, is called feature detection, and is employed as well.

Feature detection, like feature sniffing, relies on the existence of certain features in the current environment. However, like test-based fixes, feature detection is used mainly for fine-grained fixes and feature-based decision-making rather than true browser detection like feature sniffing.

A good example of this involves the events system, which we will discuss in detail in Chapter 9. There are two main event models: the standard DOM Level 2 Events model, which is supported by all major browsers, and the proprietary Internet Explorer model. In attaching events to elements, MooTools uses feature detection by checking the existence of the addEventListener method, which is the native method of the DOM Level 2 model. If it doesn't exist, MooTools then checks for the Internet Explorer model's counterpart for that method, called attachEvent. MooTools doesn't rely on the value of Browser.name, but rather, it uses feature detection to decide which event model to use.

The Wrap-Up

This chapter gave us some insight into the browser, as well as the issues involved in browser-based JavaScript. We learned how the browser renders a page, as well as the special exception given to scripts. We then found out about the two main APIs that are used for browser-based scripting, the DOM and the BOM, and we discussed a bit about their use and their structures. Finally, we learned about cross-browser issues and how MooTools performs user-agent sniffing and feature detection to make cross-browser scripting possible.

In the next chapter, we'll continue investigating browser-based JavaScript by exploring the most important API for browser-based development: the DOM Element API. We'll learn about DOM scripting and how MooTools abstracts the native DOM Element API to provide a sane and more consistent API that works across all supported browsers.

So if you're ready, turn off your mobile phone, take a seat, and applaud as we focus the spotlight on Elements.

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

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