Chapter 23. Writing Cross-Browser Event Code

The challenge in dealing with two different event models is deciding how to meld them together to make working with them as painless as possible. It's a problem that all JavaScript developers face at one point or another, and their solutions are as varied as they are. The best solutions, however, are the simplest.

In this lesson you'll look at simple solutions to cross-browser event discrepancies, and at the end of the lesson you'll add functionality to the eventUtility object.

ACQUIRING THE EVENT TARGET

The previous two lessons detailed the differences between different types of basic event information. To acquire a reference to the element object that generated the event, standards-based browsers use the target property, and Internet Explorer (IE) 8 and below use the srcElement property. Despite the two very different names, both properties provide the exact same information: the element where the event was raised.

The simplest way to acquire this information is to write a function that accepts an event object as an argument and uses the appropriate property to get the event target. Such a function could look something like this:

function getTarget(event) {
    if (typeof event.target !== "undefined") {
        return event.target;
    } else {
        return event.srcElement;
    }
}

This function, called getTarget(), determines whether to use the target or srcElement property. It's a simple solution to a simple problem. Let's add a tiny bit of complexity to it so that it fits into the patterns used by the eventUtility object. Add the following bold code to the eventUtility object:

var eventUtility = {
    addEvent : (function() {
        // removed for printing
    }()),
    removeEvent : (function() {
        // removed for printing
    }()),
    getTarget : (function() {
        if (typeof addEventListener !== "undefined") {
            return function(event) {
                return event.target;
            }
        } else {
            return function(event) {
                return event.srcElement;
            }
        }
    }())
};

This eventUtility.getTarget() method looks very much like the addEvent() and removeEvent() methods. It determines whether to use standards-based code or IE's proprietary code by checking if the addEventListener identifier is not undefined. Notice a slight change to the feature detection code. Originally the getTarget() function checked for event.target to determine standards compliance, but the eventUtility.getTarget() method uses addEventListener for detection. This is because of how getTarget() and eventUtility.getTarget() are called.

The original getTarget() function was accepting an event object as an argument, so you could use that object to determine what properties it implements. The eventUtility.getTarget() method, however, isn't being called as the result of an event; it is immediately executing when it is loaded by the browser. Since addEventListener() and event.target are both pieces of the standard event model, it's pretty safe to test for addEventListener() and assume the browser also supports the target property.

So now you have a method that you can call to get the target of an event. Let's look at how you can add more functionality to eventUtility.

CANCELING DEFAULT BEHAVIOR REDUX

Remember from Lesson 17, the lesson dealing with HTML attribute event handlers, how the event handler could return false and cancel the default action associated with the event (if any)? Well, you may or may not have noticed from the many calculator examples that returning false doesn't work with addEventListener(). You can tell it doesn't work by opening one of those pages, clicking one of the links, and looking at the URL in the location bar. You'll see a pound sign (#) at the end of the URL, as shown in Figure 23-1.

Figure 23-1

Figure 23.1. Figure 23-1

This isn't such a bad thing because seeing return false in an event handler doesn't make it very clear what that statement does. But at the same time, the ability to cancel an event's default behavior is very useful. Fortunately there is another way to cancel an event, but once again you'll have to deal with two different implementations.

The standard Event object (as supported by Chrome, Firefox, Safari, Opera, and IE9) has a method called preventDefault(). It doesn't accept any arguments; you simply call the method and the browser cancels the default behavior of the event. So instead of returning false, you simply add the following line of code to the function handling the event:

// call preventDefault() on event object to cancel event
event.preventDefault();

Canceling an event in IE8 and below requires a different approach. IE's event object has a property called returnValue, and you assign it a value of false in order to cancel the event. The following code demonstrates this:

// set returnValue as false to cancel event
event.returnValue = false;

So you have two completely different ways to perform the same action in a variety of browsers. As with the getTarget() method you added to eventUtility, a method is the simplest and most effective means of performing event cancellation across browsers. Let's skip writing a separate function and add the method to the eventUtility object. The following code adds a new method called preventDefault(). It is in bold text:

var eventUtility = {
    addEvent : (function() {
        // removed for printing
    }()),
    removeEvent : (function() {
        // removed for printing
    }()),
    getTarget : (function() {
        // removed for printing
    }()),
    preventDefault : (function() {
        if (typeof addEventListener !== "undefined") {
            return function(event) {
                event.preventDefault();
            }
        } else {
            return function(event) {
                event.returnValue = false;
            }
        }
    }())

};

The self-executing function returns one of two functions according to whether or not addEventListener is defined. Once again, it's assumed that if the browser supports addEventListener() it supports the standard Event object's preventDefault() method, as opposed to IE's returnValue property.

Now that you've added these two new pieces to the eventUtility object, let's revisit the calculator and toolbar examples.

TRY IT

In this lesson, you learn how to handle cross-browser discrepancies by writing a unified application programming interface (API) that performs work in all browsers.

Lesson Requirements

For this lesson, you will 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 Lesson23 in the JS24Hour folder you created in Lesson 1. Store the files you create in this lesson in the Lesson23 folder.

Step-by-Step

You will rewrite the toolbar script from Lesson 21 for IE. Use event delegation to handle the events for the buttons.

  1. Open eventutility.js in your text editor and add the getTarget() and preventDefault() methods to the object. They are bold in the following code:

    var eventUtility = {
        addEvent : (function() {
            if (typeof addEventListener !== "undefined") {
                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 !== "undefined") {
                return function(obj, evt, fn) {
                    obj.removeEventListener(evt, fn, false);
                };
            } else {
                return function(obj, evt, fn) {
                    obj.detachEvent("on" + evt, fn);
                };
            }
        }()),
        getTarget : (function() {
            if (typeof addEventListener !== "undefined") {
                return function(event) {
                    return event.target;
                }
            } else {
                return function(event) {
                    return event.srcElement;
                }
            }
        }()),
        preventDefault : (function() {
            if (typeof addEventListener !== "undefined") {
                return function(event) {
    event.preventDefault();
                }
            } else {
                return function(event) {
                    event.returnValue = false;
                }
            }
        }())
    };
  2. You will now write a modified version of the calculator. This new version will contain several improvements: for example, it will use the new functionality of the event utility, event delegation, and an API to control the calculator (as opposed to functions). Open another instance of your text editor and type the following code:

    <html>
    <head>
        <title>Lesson 23: Example 01</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">
        var calculator = {
            resultField : document.getElementById("results"),
            reset : function() {
                this.resultField.innerHTML = "";
            },
    
            calculate : function() {
                this.resultField.innerHTML = eval(this.resultField.innerHTML);
            },
    
            addDigit : function(digit) {
                this.resultField.innerHTML += digit;
            }
        };
        </script>
    </body>
    </html>

    As you can see from this code, the reset(), calculate(), addDigit(), and getHandlerFunction() functions are gone. Instead there is a calculator object, defined with object literal notation, and it contains a resultField property (the HTML element), a reset() method, a calculate() method, and an addDigit() method. This new calculator object is an improvement over the previous list of functions. Not only is the calculator's functionality organized into a single entity (the calculator object), but the HTML element object representing the result field is cached in the resultField property. The various methods can use this property instead of having to perform a DOM search every time they're called.

  3. Now add the event delegation code, shown in bold here:

    <html>
    <head>
        <title>Lesson 23: Example 01</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">
        var calculator = {
            resultField : document.getElementById("results"),
            reset : function() {
                this.resultField.innerHTML = "";
            },
    
            calculate : function() {
                this.resultField.innerHTML = eval(this.resultField.innerHTML);
            },
    
            addDigit : function(digit) {
                this.resultField.innerHTML += digit;
            }
        };
    
        eventUtility.addEvent(document, "click", function(event) {
            var target = eventUtility.getTarget(event);
    
            if (target.tagName.toUpperCase() === "A") {
                var innerHTML = target.innerHTML;
    
                switch (innerHTML) {
                    case "Clear":
                        calculator.reset();
    break;
    
                    case "=":
                        calculator.calculate();
                        break;
    
                    default:
                        calculator.addDigit(innerHTML);
                }
    
                eventUtility.preventDefault(event);
            }
        });
        </script>
    </body>
    </html>

    The only event you need to handle is the click event, and you need to handle it at the document level. The first line of the event handler gets the event target by using the new getTarget() method. Next, the code determines if the target is an <a/> element, and then the switch block determines what calculator method to execute. The final statement within the if block cancels the default event's behavior, and you'll see evidence of its working by running it in any browser and seeing no pound sign in the URL.

  4. Now you will modify the toolbar script to use the new getTarget() method. Open lesson22_example01.htm and save it as lesson23_example02.htm. Change the bold line in the following code:

    <html>
    <head>
        <title>Lesson 23: Example 02</title>
        <style type="text/css">
            #divContainer {
                background-color: silver;
                height: 50px;
                padding: 2px;
            }
    
            span {
                display: inline-block;
                width: 50px;
                height: 50px;
            }
    
            .button-normal {
                background-color: gray;
            }
    
    
            .button-over {
                background-color: navy;
            }
    
            .button-click {
                background-color: yellow;
    }
    </style>
    </head>
    <body>
        <div id="divContainer">
            <span class="button-normal">
    
            </span>
            <span class="button-normal">
    
            </span>
            <span class="button-normal">
    
            </span>
        </div>
        <script type="text/javascript" src="eventutility.js"></script>
        <script type="text/javascript">
        function mouseHandler(event) {
            var eSrc = eventUtility.getTarget(event),
                type = event.type;
    
            if (eSrc.tagName.toUpperCase() === "SPAN") {
                if (type === "mouseover") {
                    if (eSrc.className !== "button-click") {
                        eSrc.className = "button-over";
                    }
                } else if (type === "mouseout") {
                    if (eSrc.className !== "button-click") {
                        eSrc.className = "button-normal";
                    }
                } else if (type === "click") {
                    if (eSrc.className !== "button-click") {
                        eSrc.className = "button-click";
                    } else {
                        eSrc.className = "button-over";
                    }
                }
            }
        }
    
        eventUtility.addEvent(document, "mouseover", mouseHandler);
        eventUtility.addEvent(document, "mouseout", mouseHandler);
        eventUtility.addEvent(document, "click", mouseHandler);
        </script>
    </body>
    </html>

    It's a simple change, but the page now works in every modern browser. Go ahead and open it and you'll find it works just fine.

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

Note

Please select Lesson 23 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
18.188.57.172