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

Phát triển Javascript - part 24 ppt

10 72 0

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

THÔNG TIN TÀI LIỆU

Nội dung

ptg 10.2 Using Object Detection for Good 203 Listing 10.6 Unfriendly Host object behavior TestCase("HostObjectTest", { "test IE host object behavior": function () { var xhr = new ActiveXObject("Microsoft.XMLHTTP"); assertException(function () { if (xhr.open) { // Expectation: property exists // Reality: exception is thrown } }); assertEquals("unknown", typeof xhr.open); var element = document.createElement("div"); assertEquals("unknown", typeof element.offsetParent); assertException(function () { element.offsetParent; }); } }); In his article, “Feature Detection: State of the Art Browser Scripting” 1 , Peter Michaux provides the isHostMethod method shown in Listing 10.7 to help with feature detection and host methods. Listing 10.7 Checking if a host object is callable tddjs.isHostMethod = (function () { function isHostMethod(object, property) { var type = typeof object[property]; return type == "function" || (type == "object" && !!object[property]) || type == "unknown"; } return isHostMethod; }()); 1. http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting From the Library of WoweBook.Com Download from www.eBookTM.com ptg 204 Feature Detection This method is able to recognize callable host objects based on the following observations: • ActiveX properties always have a typeof result of "unknown." • Non-ActiveX callable host objects in Internet Explorer usually have a typeof result of "object." The boolean coercion is required to avoid null, which also has a typeof result of "object." • In other browsers, callable objects tend to have a typeof result of "function," even host methods Using this helper, we can improve our cross-browser event handler, as seen in Listing 10.8. Listing 10.8 Improved feature detection for addEventHandler function addEventHandler(element, type, listener) { if (tddjs.isHostMethod(element, "addEventListener")) { element.addEventListener(type, listener, false); } else if (tddjs.isHostMethod(element, "attachEvent") && listener.call) { element.attachEvent("on" + type, function () { return listener.call(element, window.event); }); } else { // Possibly fall back to DOM0 event properties or abort } } 10.2.4 Sample Use Testing Testing for the existence and type of an object is not always sufficient to ensure it can be used successfully. If a browser provides a buggy implementation of some feature, testing for its existence before using it will lead us straight into a trap. To avoid such buggy behavior, we can write a feature test in which we use the feature in a controlled manner before determining if the current environment supports the feature. The strftime implementation provided in Chapter 1, Automated Testing , heavily relies on the String.prototype.replace method accepting a func- tion as its second argument, a feature not available on certain older browsers. Listing 10.9 shows an implementation of strftime that uses replace in a con- trolled manner, and then defines the method only if the initial test passes. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 10.2 Using Object Detection for Good 205 Listing 10.9 Defensively defining strftime (function () { if (Date.prototype.strftime || !String.prototype.replace) { return; } var str = "%a %b"; var regexp = /%([a-zA-Z])/g; var replaced = str.replace(regexp, function (m, c) { return "[" + m + " " + c + "]"; }); if (replaced != "[%a a] [%b b]") { return; } Date.prototype.strftime = function () { /* */ }; Date.formats = { /* */ }; }()); This way the Date.prototype.strftime method will only be provided in browsers that can support it correctly. Thus, a feature test should be employed before using it, as seen in Listing 10.10. Listing 10.10 Using strftime if (typeof Date.prototype.strftime == "function") { // Date.prototype.strftime can be relied upon } // or if (typeof someDate.strftime == "function") { /* */ } Because strftime is a user-defined method, the type check should be safe. If compatibility with very old browsers was important, the strftime method could be implemented using match and a loop rather than relying on the replace method accepting a function argument. However, the point here is not necessarily gaining the widest possible support, i.e., supporting Internet Explorer 5.0 probably From the Library of WoweBook.Com Download from www.eBookTM.com ptg 206 Feature Detection isn’t your main priority. Rather, feature detection allows our scripts to know if they will succeed or not. This knowledge can be used to avoid script errors and broken web pages. Keep inmind that not only willthe featuretest avoid trouble in ancientbrowsers, it is also a safeguard for new browsers with similar problems. This is especially interesting in light of the growing number of mobile devices with JavaScript support surfing the web. On a small device with limited resources, skipping features in either the ECMAScript, DOM, or other specifications is not unthinkable. Now I don’t think String.prototype.replace will regress anytime soon, but the sample use technique is an interesting one. In Chapter 7, Objects and Prototypal Inheritance, we already saw another ex- ample of feature testing when we defined the Object.create method, which is already supported by a few browsers and will appear in more browsers as support for ECMAScript 5 becomes more widespread. 10.2.5 When to Test In the preceding sections we have seen different kinds of tests. The addEvent- Handler method applied feature tests at runtime, whereas the safeguard for Date.prototype.strftime was employed at loadtime. The runtime tests performed by addEventHandler generally provide the most reliable results be- cause they test the actual objects they operate on. However, the tests may come with a performance penalty and, more importantly, at this point it may already be too late. The overall goal of feature detection is to avoid having scripts break a web- site beyond repair. When building on the principles of unobtrusive JavaScript, the underlying HTML and CSS should already provide a usable experience. Applying feature tests up front can provide enough information to abort early if the envi- ronment is deemed unfit to run a given enhancement. However, in some cases, not all features can be reliably detected up front. If we have already partially ap- plied an enhancement only to discover that the environment will not be successful in completing the enhancement, we should take steps to roll back the changes we made. This process may complicate things, and if possible should be avoided. The roll-back situation can sometimes be avoided by deferring actions that would be destructive if applied alone. For example, in the case of the tabbed panel in Chapter 9, Unobtrusive JavaScript, we could hold off adding the class name to the panel that triggers a design that relies on the panel being fully loaded until we know that it can do so successfully. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 10.3 Feature Testing DOM Events 207 10.3 Feature Testing DOM Events Events are an integral part of most client-side web page enhancements. Most events in common use today have been available for a long time, and for most simple cases, testing for them won’t add much. However, as new events introduced by, e.g., the HTML5 spec start gaining ground, we can easily find ourselves in a situation in which we are unsure whether or not using a certain event is safe. If the event is fundamental to the use of the enhancement we’re building, we’d better test for it before we possibly mangle the web page for unsuspecting visitors. Another case is genuinely useful proprietary events such as Internet Explorer’s mouseenter and mouseleave events. Using proprietary events, or avoiding use of buggy or non-existent events, is one of those cases in which browser sniffing still is widely used. Even though some events can be tested for by triggering them programmatically, this does not hold for all events, and doing so is often cumbersome and error-prone. Juriy Zaytsev of perfectionkills.com has released an isEventSupported util- ity that makes feature testing events a breeze. Not only is using the utility simple, the implementation is based on two very simple facts as well: • Most modern browsers expose a property corresponding to supported events on element objects, i.e., "onclick" in document. documentElement is true in most browsers whereas "onjump" in document.documentElement is not. • Firefox does not expose same-named properties as the events an element supports. However, if an attribute named after a supported event is set on an element, methods of the same name are exposed. In and of itself a simple concept, the hard part is discovering it. Some browsers require relevant elements to test on in order for this to work; testing for the on- change event on a div element will not necessarily uncover if the browser sup- ports onchange. With this knowledge, we can peruse Juriy’s implementation in Listing 10.11. Listing 10.11 Feature detecting events tddjs.isEventSupported = (function () { var TAGNAMES = { select: "input", change: "input", submit: "form", From the Library of WoweBook.Com Download from www.eBookTM.com ptg 208 Feature Detection reset: "form", error: "img", load: "img", abort: "img" }; function isEventSupported(eventName) { var tagName = TAGNAMES[eventName]; var el = document.createElement(tagName || "div"); eventName = "on" + eventName; var isSupported = (eventName in el); if (!isSupported) { el.setAttribute(eventName, "return;"); isSupported = typeof el[eventName] == "function"; } el = null; return isSupported; } return isEventSupported; }()); The method uses an object as look-up for suitable elements to test a given event on. If no special case is needed, a div element is used. It then tests the two cases presented above and reports back the result. We’ll see an example of using isEventSupported in Section 10.5, Cross-Browser Event Handlers. Although the above method is good for a lot of cases, it is unfortunately not completely infallible. While working on this chapter I was informed by one of my reviewers, Andrea Giammarchi, that new versions of Chromeclaim to supporttouch events even when the device running the browser is incapable of firing them. This means that if you need to test for touch events, you should use additional tests to verify their existence. 10.4 Feature Testing CSS Properties If JavaScript is executing, surely CSS will work as well? This is a common assump- tion, and even though it is likely to be right in many cases, the two features are entirely unrelated and the assumption is dangerous to make. In general, scripts should not be overly concerned with CSS and the visual as- pects of the web page. The markup is usually the best interface between the script From the Library of WoweBook.Com Download from www.eBookTM.com ptg 10.4 Feature Testing CSS Properties 209 and CSS—add and remove class names, add, delete, or move elements and make other modifications to the DOM to trigger new CSS selectors, and by extension alternative designs. However, there are cases in which we need to adjust the presen- tational aspects from script, for instance when we need to modify dimensions and position in ways that CSS cannot express. Determining basic CSS property support is easy. For each supported CSS prop- erty, an element’s style object will provide a string property with a corresponding camel cased name. Listing 10.12 shows an example in which we check whether the current environment supports the CSS3 property box-shadow. Listing 10.12 Detecting support for box-shadow tddjs.isCSSPropertySupported = (function () { var element = document.createElement("div"); function isCSSPropertySupported(property) { return typeof element.style[property] == "string"; } return isCSSPropertySupported; }()); // True in browsers that support box-shadow assert(tddjs.isCSSPropertySupported("boxShadow")); Because the box-shadow property still lives in a draft specification, most vendors that support it does so under a vendor-specific prefix, such as -moz- and -webkit Juriy Zaytsev, who wrote the original isEventSupported, also published a getStyleProperty method, which accepts a style property, and returns the property supported in the current environment. Listing 10.13 shows its behavior. Listing 10.13 Get supported style properties // "MozBoxShadow" in Firefox // "WebkitBoxShadow" in Safari // undefined in Internet Explorer getStyleProperty("boxShadow"); This method can be useful in some cases, but the test is not very strong. Even though the property exists as a string on an element’s style property, the browser may have problems with its implementation of the property. Ryan Morr has written a isStyleSupported method that uses getComputedStyle in From the Library of WoweBook.Com Download from www.eBookTM.com ptg 210 Feature Detection supporting browsers, and runtimeStyle in Internet Explorer to check if the browser accepts specific values for various properties. The method can be found at http://ryanmorr.com/archives/detecting-browser-css-style-support. 10.5 Cross-Browser Event Handlers As illustrated throughout this chapter, event handling is not a cross-browser picnic. To see a more complete example of how to utilize feature detection to harden scripts, we will add a cross-browser addEventHandler function to the tddjs namespace, which we will use in Part III, Real-World Test-Driven Development in JavaScript. The API will only be created if the current environment is deemed able to support it. The method needs either addEventListener or attachEvent to work. Falling back to event properties is not sufficient unless we build a registry on top of them, allowing addEventHandler still to accept several handlers for an event on a specific element. This is possible, but considering the browser’s such a solution would likely target, probably not worth the effort or the added weight. Further, we test for Function.prototype.call, which is needed in the attachEvent branch. The final method can be seen in Listing 10.14. Listing 10.14 Feature detection based cross-browser event handling (function () { var dom = tddjs.namespace("dom"); var _addEventHandler; if (!Function.prototype.call) { return; } function normalizeEvent(event) { event.preventDefault = function () { event.returnValue = false; }; event.target = event.srcElement; // More normalization return event; } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 10.5 Cross-Browser Event Handlers 211 if (tddjs.isHostMethod(document, "addEventListener")) { _addEventHandler = function (element, event, listener) { element.addEventListener(event, listener, false); }; } else if (tddjs.isHostMethod(document, "attachEvent")) { _addEventHandler = function (element, event, listener) { element.attachEvent("on" + event, function () { var event = normalizeEvent(window.event); listener.call(element, event); return event.returnValue; }); }; } else { return; } dom.addEventHandler = _addEventHandler; }()); This implementation is not complete; for instance, the event object is not suf- ficiently normalized. Because details are less important than the overall concept in this example, I leave further normalization as an exercise to the reader. Note that the event object is a host object, and so you may not be comfortable adding properties on it. An alternative approach could be to return a regular object that maps calls to the event object. tddjs.dom.addEventHandler operates as a proxy for registering event handlers, opening the door to supporting custom events. One example of such a custom event is the proprietary mouseenter event mentioned previously, only supported by Internet Explorer. The mouseenter event only fires once as the mouse enters the bounds of an element. This is more helpful than mouseover in many cases, as event bubbling causes the latter to fire every time the user’s mouse enters one of the target element’s descendants, not only when the mouse enters the target element. To allow for custom events, we can wrap the _ addEventHandler function and have it first look for custom events in the dom.customEvents namespace. The mouseenter implementation is added to this namespace only if the environ- ment does not already support it—we don’t want to override a native event with an inferior version—and if the required mouseover and mouseout events are supported. Listing 10.15 shows a possible implementation. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 212 Feature Detection Listing 10.15 Custom event handlers in addEventHandler (function () { /* */ function mouseenter(el, listener) { var current = null; _addEventHandler(el, "mouseover", function (event) { if (current !== el) { current = el; listener.call(el, event); } }); _addEventHandler(el, "mouseout", function (e) { var target = e.relatedTarget || e.toElement; try { if (target && !target.nodeName) { target = target.parentNode; } } catch (exp) { return; } if (el !== target && !dom.contains(el, target)) { current = null; } }); } var custom = dom.customEvents = {}; if (!tddjs.isEventSupported("mouseenter") && tddjs.isEventSupported("mouseover") && tddjs.isEventSupported("mouseout")) { custom.mouseenter = mouseenter; } dom.supportsEvent = function (event) { return tddjs.isEventSupported(event) || !!custom[event]; }; function addEventHandler(element, event, listener) { if (dom.customEvents && dom.customEvents[event]) { From the Library of WoweBook.Com Download from www.eBookTM.com . "unknown"; } return isHostMethod; }()); 1. http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting From the Library of WoweBook.Com Download from www.eBookTM.com ptg 204 Feature. at http://ryanmorr.com/archives/detecting-browser-css-style-support. 10.5 Cross-Browser Event Handlers As illustrated throughout this chapter, event handling is not a cross-browser picnic. To see a more. harden scripts, we will add a cross-browser addEventHandler function to the tddjs namespace, which we will use in Part III, Real-World Test-Driven Development in JavaScript. The API will only be

Ngày đăng: 04/07/2014, 22:20

TỪ KHÓA LIÊN QUAN