Chapter 20. Writing Cross-Browser Event Handlers

In ye olden days, the common approach to cross-browser scripting was similar to the solution at the end of Lesson 19: Create multiple pages for each browser type. The solution worked, but the amount of time involved grew according to how many different browsers you wanted to support. As years moved on, the accepted approach was simply to branch code (using an if statement to determine what method to use) when needed, like this:

// assume el variable is an element object
if (typeof addEventListener === "function") {
    el.addEventListener("click", function() {
        alert("You clicked me!");
    }, false);
} else if (typeof attachEvent !== "undefined") {
    el.attachEvent("onclick", function() {
        alert("You clicked me!");
    });
} else {
    el.onclick = function() {
        alert("You clicked me!");
    };
}

What you see here is a type of browser detection called feature detection — the detection of certain features, like addEventListener(). The idea is to determine whether the browser supports a particular feature and, if so, to use it. If the feature isn't supported, you fall back to a feature you know will work. In the preceding example the code checks for the addEventListener() method (which all standards-compliant browsers support) and uses it if it is found. If the browser does not support addEventListener(), then the code attempts to identify the browser as a version of IE and, if so, uses attachEvent(). If the browser does not support either addEventListener() or attachEvent(), then the code falls back to the DOM Level 0 onclick event handler.

Note

The JavaScript engine in IE8 and below can do quirky things because of how different components in the browser are made. For example typeof attachEvent actually returns "object" instead of "function." That is why the preceding code checks if attachEvent is not undefined. User-defined functions, however, are type "function."

Most of the time, however, you write code for two types of browsers: standard compliant browsers and IE8 and below. So, the preceding code can be simplified to the following:

// assume el variable is an element object
if (typeof addEventListener === "function") {
    el.addEventListener("click", function() {
        alert("You clicked me!");
    }, false);
} else {
    el.attachEvent("onclick", function() {
        alert("You clicked me!");
    });
}

This code follows the same idea as before, except now attachEvent() is the fallback instead of the DOM Level 0 onclick event handler.

How you check for a feature is important. Nine times out of ten you want to check for standards compliance first because the standards will rarely change. For example, you can check for attachEvent() first, such as this:

// WRONG!
if (typeof attachEvent !== "undefined") {
    el.attachEvent("onclick", function() {
        alert("You clicked me!");
    });
} else {
    el.addEventListener("click", function() {
        alert("You clicked me!");
    }, false);
}

This does work, but consider the case of IE 9. It supports not only its own proprietary event model, but the standard event model as well. As a developer, you want to use the features put forth by the standards as much as possible, and you can ensure that this happens by checking for standard features before proprietary ones.

Now, in the old days, code branching such as this would be littered throughout a JavaScript file. Naturally, the next evolution was to refactor that code into its own function, like this:

function setEventHandler(obj, evt, fn) {
    if (typeof addEventListener === "function") {
        obj.addEventListener(evt, fn, false);
    } else {
        obj.attachEvent("on" + evt, fn);
}
}

setEventHandler(el, "click", function() {
    alert("You clicked me!");
});

This code creates a function called setEventHandler(). It accepts three arguments: the object to assign an event handler to, the name of the event to handle (without the on prefix), and the function to handle the event. Inside the function, an if statement determines whether addEventListener() or attachEvent() should be used to assign an event handler to the provided object. Notice the call to attachEvent() inside the else block. Remember that the name of the event was passed to the function without the on prefix, so the prefix must be added in the call to attachEvent().

The final statement of this code shows how the function is called by adding a click event handler on an element object. This way you call the setEventHandler() function whenever you need to wire up events for a particular object. But this approach, too, has its own drawbacks — one being suboptimal performance. Every call to setEventHandler() causes a slight performance hit for the following reasons:

  • The JavaScript engine has to make a decision before it can execute addEventListener() or attachEvent(). This decision takes longer than simply executing one of the event handler methods.

  • The engine has to look up the addEventListener identifier.

These problems bring us to modern JavaScript techniques, which you study in this lesson.

WRITING AN EVENT UTILITY OBJECT

Every aspect of JavaScript programming in the browser is object-oriented. Naturally, any tools and utilities you write should also incorporate object-oriented concepts that organize your code into one or more objects. In the case of events, you'll begin work on an object containing utility methods that work in all modern browsers.

Start by creating an object called eventUtility, shown in the following code:

var eventUtility = {};

This code uses object literal notation to create a singleton object. You do not need to create a new data type for this utility because it does not need any instance data; it's simply a utility in which all the necessary data is passed to its methods. Now add an if statement to branch your code, as shown before. Check if addEventListener is a feature supported by the browser:

var eventUtility = {};

if (typeof addEventListener === "function") {

} else {

}

Now the eventUtility object needs a method to add event handlers to an object. Call this method addEvent, and assign it a function that calls either addEventListener() or attachEvent(), such as the following code in bold:

var eventUtility = {};

if (typeof addEventListener === "function") {
    eventUtility.addEvent = function(obj, evt, fn) {
        obj.addEventListener(evt, fn, false);
    };
} else {
    eventUtility.addEvent = function(obj, evt, fn) {
        obj.attachEvent("on" + evt, fn);
    };
}

This new code creates an addEvent() method as a member of the eventUtility object. Actually, there are two definitions of addEvent(), but only one is actually assigned to eventUtility.addEvent. Notice the three parameters of addEvent() in both declarations; they are the same. This is very important because you want to be able to call addEvent() and have it work correctly regardless of what browser the user is using. The following code will add an event handler to an element to handle the click event, and it'll work in any modern browser (even IE6):

eventUtility.addEvent(el, "click", function() {
    alert("You clicked me!");
});

Let's take a look and see how this is more advantageous to the setEventHandler() function shown at the beginning of this lesson.

  • Now the if statement, with its attendant performance problems (with the addEventListener lookup and the actual decision), is executed only once — when the browser loads the code. Unfortunately, this decision cannot be circumvented.

  • The addEvent() method is immediately available to call because the browser executes the if ... else statement and assignment operations immediately after the browser loads the JavaScript code — much as it does with the setEventHandler() function.

  • Calls to addEvent() execute only the code needed to assign an event handler to an object. There are no decisions or extraneous lookups for other identifiers. This is in contrast to setEventHandler(), which needs extra code in order to work.

Even though the eventUtility object makes working with events a little easier (and faster), the implementation doesn't quite match that of techniques used by modern-day JavaScript experts. So let's look at how this code can be transformed into modern-day JavaScript.

MODERNIZING YOUR EVENT UTILITY

An updated eventUtility object starts as before with an object literal:

var eventUtility = {};

Next, add a method called addEvent() to the object, like this:

var eventUtility = {
    addEvent : function() {

    }
};

Notice that this method does not specify any parameters. That is because this function declaration is actually going to execute and return another function to addEvent. Let's add some of that code now:

var eventUtility = {
    addEvent : function() {

    }()
};

The only thing this code adds is a set of parentheses at the end of the function statement. Remember that in order for a function to execute, the function's identifier must be followed by a set of parentheses, as in this call to alert():

alert("Hello, JavaScript!");

You can apply the same concept to anonymous functions by simply adding a set of parentheses to the end of the function statement, like this:

function() {
    alert("Hello, JavaScript!");
}();

This code doesn't actually work because it has a syntax error, but you can make it work by surrounding the function and parentheses with another set of parentheses, like this:

(function() {
    alert("Hello, JavaScript!");
}());

So when the JavaScript engine loads this code, it executes the anonymous function that, in turn, executes the alert() method inside it. This type of function is sometimes referred to as a self-executing function.

Returning to your event utility object, you have an anonymous function that executes after the browser loads the JavaScript code, but it isn't returning anything worthwhile to addEvent. So let's change that with the following bold code:

var eventUtility = {
    addEvent : (function() {
if (typeof addEventListener === "function") {
            return function(obj, evt, fn) {
                obj.addEventListener(evt, fn, false);
            };
        }
    }())
};

This new code first encapsulates the self-executing function in parentheses. It then adds an if block that looks somewhat similar to code in your original eventUtility code; it returns a function to addEvent as opposed to directly assigning the function to eventUtility.addEvent as in the previous version. Let's finish this new version by adding an else block for browsers that do not support addEventListener. The new code is in bold:

var eventUtility = {
    addEvent : (function() {
        if (typeof addEventListener === "function") {
            return function(obj, evt, fn) {
                obj.addEventListener(evt, fn, false);
            };
        } else {
            return function(obj, evt, fn) {
                obj.attachEvent("on" + evt, fn, false);
            };
        }
    }())
};

Once again, the contents of the else block are somewhat similar to those of the previous version. The main difference is that a function is returned to addEvent instead of being assigned directly; the end result, however, is the same. When the self-executing function does its thing, it returns one of two functions to addEvent; which function depends on what event model the browser supports.

Note

This event utility, as shown in this lesson, does not offer a means to remove an event handler. However, the code in the Try It section, as well as the code download, does. The functionality operates according to the same principles discussed in this lesson.

TRY IT

In this lesson, you learn how to write your own event utility to handle cross-browser issues.

Lesson Requirements

For this lesson, you need a text editor; any plain text editor will do. For Microsoft Windows users, Notepad is available by default on your system or you can download Microsoft's free Visual Web Developer Express (www.microsoft.com/express/web/) or Web Matrix (www.asp.net/webmatrix/). Mac OS X users can use TextMate, which comes as part of OS X, or download a trial for Coda (www.panic.com/coda/). Linux users can use the built-in VIM.

You also need a modern web browser. Choose any of the following:

  • Internet Explorer 8+

  • Google Chrome

  • Firefox 3.5+

  • Apple Safari 4+

  • Opera 10+

Create a subfolder called Lesson20 in the JS24Hour folder you created in Lesson 1. Store the files you create in this lesson in the Lesson20 folder.

Step-by-Step

You will rewrite the calculator application from Lessons 17, 18, and 19 using the event utility object.

  1. Open your text editor and type the following JavaScript:

    var eventUtility = {
        addEvent : (function() {
            if (typeof addEventListener === "function") {
                return function(obj, evt, fn) {
                    obj.addEventListener(evt, fn, false);
                };
            } else {
                return function(obj, evt, fn) {
                    obj.attachEvent("on" + evt, fn);
                };
            }
        }()),
        removeEvent : (function() {
            if (typeof removeEventListener === "function") {
                return function(obj, evt, fn) {
                    obj.removeEventListener(evt, fn, false);
                };
            } else {
                return function(obj, evt, fn) {
                    obj.detachEvent("on" + evt, fn);
                };
            }
        }())
    };

    Save it as eventutility.js.

  2. Now open another instance of your text editor and type the following HTML:

    <html>
    <head>
        <title>Lesson 20: Example 02</title>
        <style type="text/css">
        td {
            border: 1px solid gray;
            width: 50px;
        }
    
        #results {
            height: 20px;
        }
        </style>
    </head>
    <body>
        <table border="0" cellpadding="2" cellspacing="2">
            <tr>
                <td colspan="4" id="results"></td>
            </tr>
            <tr>
                <td><a href="#">1</a></td>
                <td><a href="#">2</a></td>
                <td><a href="#">3</a></td>
                <td><a href="#">+</a></td>
            </tr>
            <tr>
                <td><a href="#">4</a></td>
                <td><a href="#">5</a></td>
                <td><a href="#">6</a></td>
                <td><a href="#">-</a></td>
            </tr>
            <tr>
                <td><a href="#">7</a></td>
                <td><a href="#">8</a></td>
                <td><a href="#">9</a></td>
                <td><a href="#">*</a></td>
            </tr>
            <tr>
                <td><a href="#">Clear</a></td>
                <td><a href="#">0</a></td>
                <td><a href="#">=</a></td>
                <td><a href="#">/</a></td>
            </tr>
        </table>
        <script type="text/javascript" src="eventutility.js"></script>
        <script type="text/javascript">
        function addDigit(digit) {
            var resultField = document.getElementById("results");
    
            resultField.innerHTML += digit;
    
            return false;
    }
    
        function calculate() {
            var resultField = document.getElementById("results");
    
            resultField.innerHTML = eval(resultField.innerHTML);
    
            return false;
        }
    
        function reset() {
            var resultField = document.getElementById("results");
    
            resultField.innerHTML = "";
    
            return false;
        }
    
        function getHandlerFunction(innerHTML) {
            return function() {
                addDigit(innerHTML);
    
                return false;
            };
        }
    
        onload = function() {
            var links = document.getElementsByTagName("a"),
                length = links.length;
    
            for (var i = 0; i < length; i++) {
                var link = links[i],
                    innerHTML = link.innerHTML,
                    func = null;
    
                switch (innerHTML) {
                    case "Clear":
                        func = reset;
                        break;
    
                    case "=":
                        func = calculate;
                        break;
    
                    default:
                        func = getHandlerFunction(innerHTML);
                }
    
                eventUtility.addEvent(link, "click", func);
            }
        };
        </script>
    </body>
    </html>

    Save it as lesson20_example01.htm. Not much changed in this version. The eventUtility object contained within the external JavaScript file is referenced, and the utility is used to assign the click event handlers to the <a/> element objects. Open this file in multiple browsers and you'll find that it works in them all.

To get the sample code files, you can download Lesson 20 from the book's website at www.wrox.com.

Note

Please select Lesson 20 on the DVD to view the video that accompanies this lesson.

Step-by-Step
..................Content has been hidden....................

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