1. Trang chủ
  2. » Công Nghệ Thông Tin

Test Driven JavaScript Development- P11 potx

20 246 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 194,3 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Browser sniffing mainly comes in two flavors; user agent sniffing and object detection.. Listing 10.1 Browser sniffing to fix event listening function addEventHandlerelement, type, listener {

Trang 1

Listing 9.10 Using the tab controller

(function () {

if (typeof document == "undefined" ||

!document.getElementById) { return;

} var dom = tddjs.dom;

var ol = document.getElementById("news-tabs");

/* */

try { var controller = tddjs.ui.tabController.create(ol);

dom.addClassName(ol.parentNode, "js-tabs");

controller.onTabChange = function (curr, prev) { dom.removeClassName(getPanel(prev), "active-panel");

dom.addClassName(getPanel(curr), "active-panel");

};

controller.activateTab(ol.getElementsByTagName("a")[0]);

} catch (e) {}

}());

The getPanel function used in the above example uses the semantic markup

to find which panel an anchor should toggle It extracts the part of the anchor’s href attribute after the hash character, looks up elements with corresponding names, and finally picks the first one it finds It then traverses the element’s parent until it finds

a div element The method can be seen in Listing 9.11

Listing 9.11 Finding the panel to toggle

(function () { /* */

function getPanel(element) {

if (!element || typeof element.href != "string") { return null;

} var target = element.href.replace(/.*#/, "");

var panel = document.getElementsByName(target)[0];

Trang 2

while (panel && panel.tagName.toLowerCase() != "div") { panel = panel.parentNode;

} return panel;

} /* */

}());

Note that getPanel defensively checks its argument and aborts if it doesn’t receive an actual element This means that we can fearlessly call it using the curr

and prev anchors in the onTabChange method, even though the prev argument

will be undefined the first time it is called

To make the tabbed panels appear as panels, we can sprinkle on some very simple CSS, as seen in Listing 9.12

Listing 9.12 Simple tabbed panel CSS

.js-tabs section {

clear: left;

display: none;

}

.js-tabs active-panel {

display: block;

}

.js-tabs nav {

border-bottom: 1px solid #bbb;

margin: 0 0 6px;

overflow: visible;

padding: 0;

}

.js-tabs nav li {

display: inline;

list-style: none;

}

.js-tabs nav a {

background: #eee;

border: 1px solid #bbb;

line-height: 1.6;

Trang 3

padding: 3px 8px;

} js-tabs a.active-tab { background: #fff;

border-bottom-color: #fff;

color: #000;

text-decoration: none;

}

All the style rules are prefixed with “.js-tabs”, which means that they will only take effect if the script in Listing 9.10 completes successfully Thus, we have a nice tabbed panel in browsers that support it and fall back to inline bookmarks and vertically presented panels of text in unsupporting browsers

Implementation of the unobtrusive tabs might strike you as a bit verbose and

it is not perfect It is, however, a good start—something to build on For instance, rather than coding the panel handling inline as we just did, we could create a tabbedPanelobject to handle everything Its create method could receive the outer div element as argument and set up a tabController and offer something like the getPanel function as a method It could also improve the current solution

in many ways, for example, by checking that the tabs do not activate panels outside the root element

By implementing the tabController separately, it can easily be used for similar, yet different cases One such example could be building a tabbed panel widget in which the links referenced external URLs The onTabChange callback could in this case be used to fetch the external pages using XMLHttpRequest By design, this tabbed panel would fall back to a simple list of links just like the panel

we just built

Because the original unobtrusive example used the jQuery library, we could

of course have done so here as well By using it where appropriate, we’d end up shaving off quite a few lines of code However, although the script would end up shorter, it would come with an additional 23kB (minimum) of library code The unobtrusive tab controller we just built weigh in at less than 2kB, have no external dependencies, and work in more browsers

As a final note, I want to show you a compact idiomatic jQuery solution as well Listing 9.13 shows the tabbed panel implemented in about 20 lines of (heavily wrapped) code Note that this solution does not check markup before enabling the panel, and cannot be reused for other similar problems in a meaningful way

Trang 4

Listing 9.13 Compact jQuery tabbed panels

jQuery.fn.tabs = function () {

jQuery(this)

addClass("js-tabs")

find("> ol:first a")

live("click", function () { var a = jQuery(this);

a.parents("ol").find("a").removeClass("active-tab");

a.addClass("active-tab");

jQuery("[name="+this.href.replace(/^.*#/, "") + "]")

parents("div")

addClass("active-panel")

siblings("div.section")

removeClass("active-panel");

});

return this;

};

9.6 Summary

In this chapter we have discussed the principles of unobtrusive JavaScript and how

they can help implement websites using progressive enhancement A particularly

obtrusive implementation of tabbed panels served to shed some light on the

prob-lems caused by making too many assumptions when coding for the client

Unobtrusive JavaScript describes clean code the JavaScript way, including stay-ing clean in its interaction with its surroundstay-ings, which on the web must be assumed

to be highly unstable and unpredictable

To show how unobtrusive code can be implemented to increase accessibility potential, lower error rates, and provide a more maintainable solution, we snuck

a peek into a test-driven development session that culminated in an unobtrusive

tabbed panel that works in browsers as old as Internet Explorer 5.0, uses no external

library, and disables itself gracefully in unsupporting environments

In Chapter 10, Feature Detection, we will take the concept of making no

as-sumptions even further, and formalize some of the tests we used in this chapter as

we dive into feature detection, an important part of unobtrusive JavaScript

Trang 5

10

Feature Detection

Aspiring JavaScript developers developing for the general web are faced with a rather unique challenge, in that very little is known about the environments in which scripts will execute Even though we can use web analytics to gather information about our visitors, and external resources such as Yahoo’s graded browser support

to guide us in decisions relevant to cross-browser development, we cannot fully trust these numbers; neither can they help make our scripts future proof

Writing cross-browser JavaScript is challenging, and the number of available browsers is increasing Old browsers see new version releases, the occasional new browser appears (the most recent noticeable one being Google Chrome), and new platforms are increasingly becoming a factor The general web is a minefield, and our task is to avoid the mines Surely we cannot guarantee that our scripts will run effortlessly on any unknown environment lurking around the Internet, but we should be doing our very best to avoid ruining our visitors’ experience based on bad assumptions

In this chapter we will dive into the technique known as feature detection, arguably the strongest approach to writing robust cross-browser scripts We will see how and why browser detection fails, how feature detection can be used in its place, and how to use feature detection to allow scripts to adjust in response to collecting knowledge about the environment’s capabilities

Trang 6

10.1 Browser Sniffing

For as long as there has been more than one browser in popular use, developers

have tried to differentiate between them to either turn down unsupported browsers,

or provide individual code paths to deal with differences between them Browser

sniffing mainly comes in two flavors; user agent sniffing and object detection

10.1.1 User Agent Sniffing

Sniffing the user agent is a primitive way of detecting browsers By inspecting the

contents of the User-Agent HTTP header, accessible through navigator

userAgent, script authors have branched their scripts to run IE specific code for

IE and Netscape-specific code for Netscape, or commonly, deny access to

unsup-ported browsers Unwilling to have their browsers discriminated against, browser

vendors adjusted the User-Agent header sent by the browser to include strings

known to allow the browser access This is evident to this day; Internet Explorer still

includes the word “Mozilla” in its user agent string and Opera stopped identifying

itself as Internet Explorer not too long ago

As if browsers with built-in lies weren’t enough, most browsers today even allow their users to manually choose how the browser should identify itself That’s about

as unreliable identification as you can find

Event handling has traditionally been rocky terrain to cover consistently across

browsers The simple event properties we used in Chapter 9, Unobtrusive JavaScript,

is supported by just about any browser in use today, whereas the more sophisticated

EventListenerinterface from the level 2 DOM specification is not The spec

calls for any Node to implement this interface, which among other things define

the addEventListener method Using this method we can add numerous event

listeners to an event for a specific element, and we needn’t worry about the event

property accidentally being overwritten

Most browsers available today support the addEventListener method, unfortunately with the exception of Internet Explorer (including version 8) IE

does, however, provide the attachEvent method, which is similar and can be

used to emulate common use cases A naive way to work around this could involve

the use of user agent sniffing, as seen in Listing 10.1

Listing 10.1 Browser sniffing to fix event listening

function addEventHandler(element, type, listener) {

// Bad example, don't try this at home

if (/MSIE/.test(navigator.userAgent)) {

Trang 7

element.attachEvent("on" + type, function () { // Pass event as argument to the listener and // correct it's this value IE calls the listener // with the global object as this

return listener.call(element, window.event);

});

} else { element.addEventListener(type, listener, false);

} }

This piece of code makes many mistakes, but alas, is representative of lots of code

in use even to this day The user agent sniff is potentially dangerous in a couple of ways; it assumes that any browser that does not appear to be Internet Explorer sup-ports addEventListener; it assumes that any browser appearing to be Internet Explorer supports attachEvent, and makes no room for a future Internet Ex-plorer that supports the standardized API In other words, the code will err on some browsers and definitely will need updating whenever Microsoft releases a standards-compliant browser We will improve on the example throughout this chapter

10.1.2 Object Detection

As sniffing the user agent string became increasingly hard due to dishonest browsers, browser detection scripts grew more sophisticated Rather than inspecting the user agent string, developers discovered that the type of browser could very often be determined by checking for the presence of certain objects For instance, the script

in Listing 10.2 updates our previous example to avoid the user agent string and rather infer type of browser based on some objects known to exist only in Internet Explorer

Listing 10.2 Using object detection to sniff browser

function addEventHandler(element, type, listener) { // Bad example, don't try this at home

if (window.ActiveXObject) { element.attachEvent("on" + type, function () { return listener.call(element, window.event);

});

} else { element.addEventListener(type, listener, false);

} }

Trang 8

This example suffers many of the same problems as that of our user agent sniffer

Object detection is a very useful technique, but not to detect browsers.

Although unlikely, there is no guarantee that browsers other than Internet Ex-plorer won’t provide a global ActiveXObject property For instance, older

ver-sions of Opera imitated several aspects of Internet Explorer, such as the

propri-etary document.all object, to avoid being blocked by scripts that employed bad

browser detection logic

The basic premise of browser detection relies on upfront knowledge about the environments that will run our scripts Browser detection, in any form, does not

scale, is not maintainable, and is inadequate as a cross-browser scripting strategy

10.1.3 The State of Browser Sniffing

Unfortunately, browser detection still exists in the wild Many of the popular libraries

still to this day use browser detection, and even user agent sniffing, to solve certain

cross-browser challenges Do a search for userAgent or browser in your favorite

JavaScript library, and more likely than not, you will find several decisions made

based on which browser the script thinks it’s faced with

Browser sniffs cause problems even when they are used only to make certain exceptions for certain browsers, because they easily break when new browser

ver-sions are released Additionally, even if a sniff could be shown to positively

iden-tify a certain browser, it cannot be easily shown to not accidentally ideniden-tify other

browsers that may not exhibit the same problems the sniffs were designed to smooth

over

Because browser detection frequently requires updating when new browsers are released, libraries that depend on browser sniffs put a maintenance burden on

you, the application developer To make the situation even worse, these updates are

not necessarily backwards compatible, and may require you to rewrite code as well

Using JavaScript libraries can help smooth over many difficult problems, but often

come at a cost that should be carefully considered

10.2 Using Object Detection for Good

Object detection, although no good when used to detect browsers, is an excellent

technique for detecting objects Rather than branching on browser, a much sounder

approach is branching on individual features Before using a given feature, the script

can determine whether it is available, and in cases in which the feature is known to

have buggy implementations, the script can test the feature in a controlled setting

to determine if it can be relied upon This is the essence of feature detection

Trang 9

10.2.1 Testing for Existence

Consider once again our event handling example Listing 10.3 uses object detection

as before, but rather than testing objects known to only exist in certain browsers, it tests the objects we’re actually interested in using

Listing 10.3 Using feature detection to branch event handling

function addEventHandler(element, type, listener) {

if (element.addEventListener) { element.addEventListener(type, listener, false);

} else if (element.attachEvent && listener.call) { element.attachEvent("on" + type, function () { return listener.call(element, window.event);

});

} else { // Possibly fall back to event properties or abort }

} This example has a much better chance of surviving in the wild, and is very un-likely to need updating whenever a new browser is released Internet Explorer 9 is scheduled to implement addEventListener, and even if this browser keeps attachEvent side by side with it to ensure backwards compatibility, our addEventHandleris going to do the right thing Prodding for features rather than browser type means our script will use addEventListener if it’s available without any manual interference The preceding browser detection-based scripts will all have to be updated in such a scenario

10.2.2 Type Checking

Although Listing 10.3 prods the correct objects before using them, the feature test

is not completely accurate The fact that the addEventListener property exists

is not necessarily a guarantee that it will work as expected The test could be made more accurate by checking that it is callable, as Listing 10.4 shows

Listing 10.4 Type-checking features

function addEventHandler(element, type, listener) {

if (typeof element.addEventListener == "function") { element.addEventListener(type, listener, false);

} else if (typeof element.attachEvent == "function" &&

typeof listener.call == "function") { element.attachEvent("on" + type, function () {

Trang 10

return listener.call(element, window.event);

});

} else { // Possibly fall back to DOM0 event properties or abort }

}

This example employs more specific feature tests, and should ideally produce fewer false positives Unfortunately, it does not work at all in certain browsers To

understand why, we need to familiarize ourselves with native and host objects

10.2.3 Native and Host Objects

Any object whose semantics are described by the ECMAScript specification is

known as a native object By the nature of their definition, the behavior of native

objects is generally predictable and, as such, using specific feature tests such as the

type-check in Listing 10.4 will usually provide valuable information However, given

a buggy environment, we may encounter a browser whose typeof implementation

is doing the wrong thing even if the object in question is in fact callable and works

as expected By making a feature test more specific we reduce the chances of false

positives, but at the same time we demand more from the environment, possibly

increasing the chances of false negatives

Objects provided by the environment but not described by the ECMAScript

specification are known as host objects For example, a browser’s DOM

implemen-tation consists solely of host objects Host objects are problematic to feature test

because the ECMAScript specification defines them very loosely;

“implementation-defined” is commonly found in the description of host object behavior

Host objects are, among other things, afforded the luxury of defining their own result for typeof In fact, the third edition of the ECMAScript specification does

not restrict this result in any way, and host objects may return “undefined” when

used with typeof, should they so wish Although attachEvent most definitely

is callable in Internet Explorer, the browser is not cooperative in purveying this

information when asked with typeof, as Listing 10.5 shows

Listing 10.5 typeof and host objects in Internet Explorer

// true in Internet Explorer, including version 8

assertEquals("object", typeof document.attachEvent);

As if this result wasn’t bad enough, other host objects such as ActiveX objects are even worse to work with Listing 10.6 shows a few surprising results

Ngày đăng: 03/07/2014, 05:20