views:

56

answers:

2

Hi,

I have the code included below that allows me to do something the first time a user mouses over an element and then remove the event.

It plays nice in the W3C event model browsers but keeps throwing an error in IE6-8. I got the code from another question and believed it would handle IE. Anyone see what I'm doing wrong?

    <script type="text/javascript">
    function setMouseEvent() {
        //Tel: 01 8279 400
        event = addEvent(document.getElementById('contactButton'), 'mouseover', changeText);
    }

    function changeText() {
        alert("worked!");
        removeEvent(document.getElementById('contactButton'), 'mouseover', changeText);
    }

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

    function removeEvent(obj, type, fn) {
        if (typeof obj.addEventListener != undefined) {
            obj.removeEventListener(type, fn, false);
        }
        else if (typeof obj.attachEvent != undefined) {
            obj.detachEvent("on" + type, obj[type + fn]);
            obj[type + fn] = null;
            obj["e" + type + fn] = null;
        }
    }

    window.onload = setMouseEvent;
</script>

Update: i just tested in the latest Chrome, Opera and FF with no issues but Safari does nothing when I mouse over and IE is throwing the errors onload as mentioned.

+1  A: 

Go for Dean's addEvent, if you want a flexible solution:

// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
    if (element.addEventListener) {
        element.addEventListener(type, handler, false);
    } else {
        // assign each event handler a unique ID
        if (!handler.$$guid) handler.$$guid = addEvent.guid++;
        // create a hash table of event types for the element
        if (!element.events) element.events = {};
        // create a hash table of event handlers for each element/event pair
        var handlers = element.events[type];
        if (!handlers) {
            handlers = element.events[type] = {};
            // store the existing event handler (if there is one)
            if (element["on" + type]) {
                handlers[0] = element["on" + type];
            }
        }
        // store the event handler in the hash table
        handlers[handler.$$guid] = handler;
        // assign a global event handler to do all the work
        element["on" + type] = handleEvent;
    }
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
    if (element.removeEventListener) {
        element.removeEventListener(type, handler, false);
    } else {
        // delete the event handler from the hash table
        if (element.events && element.events[type]) {
            delete element.events[type][handler.$$guid];
        }
    }
};

function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
    // get a reference to the hash table of event handlers
    var handlers = this.events[event.type];
    // execute each event handler
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

function fixEvent(event) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};
galambalazs
+5  A: 
event = addEvent(

addEvent doesn't return anything; you're assigning the undefined lack-of-return-value to a global variable event (since you've not said var event in the function). This will cause an exception in IE, which uses the global event as a special object to pass event details to handlers, and consequently doesn't allow you to assign something else to it.

if (typeof obj.addEventListener != undefined)

typeof always returns a string, which will never test equal to the undefined unvalue, so IE will always take the non-IE fork and fail. You meant if (typeof obj.addEventListener !== 'undefined'), with a string.

obj.detachEvent("on" + type, obj[type + fn]);

Since you've not written a property with the name of type and fn stuck together in the addEvent function, this will fail to retrieve anything.

As Crescent says, it looks like you're using Resig's removeEvent function, paired with a different addEvent that doesn't match. If you use his removeEvent you will need to use his addEvent to go with it.

However, I wouldn't use these functions anyway: they're pretty dodgy. I know it was 2005 when this ill-conceived code ‘won’ quirksmode's addEvent contest, but even then we should have known a lot better. The problem is it creates a string from the event name and the textual serialisation of the function code (type+fn), and uses it as a key to store a callback. This key will look something like 'mouseoverfunction changeText() {...code...}'.

But relying on function serialisation is a terrible idea. The format is not standardised by ECMAScript (“An implementation-dependent representation of the function is returned.”); there are many browser quirks; and not least, two different functions can easily return the same string for browsers' current serialisation methods:

var f1= function() {};
var f2= function() {};

f1 and f2 will have the same string serialisation, but they're not the same object. If you did an addEvent for f1 on IE, and then another one for f2, the second property would use the same serialised string and overwrite the first property. Then calling removeEvent for f1 would retrieve the function for f2, try to detachEvent it and fail because it's not the same function. This example may look contrived, but it's actually extremely easy to do accidentally when you're using generic closures, as modern JavaScript does more and more these days. For this reason I'd recommend avoiding Resig's addEvent in all circumstances.

(jQuery users: don't worry, for all its problems jQuery does not fall into the trap of using this code.)

This hack is there to preserve the this value when your changeText function is called back by IE, whose attachEvent doesn't set this. But you don't even use the this value, so you could have got away with a much simpler version, such as the original addEvent it was created to replace.

At least the shortcomings of that addEvent are well-known and show up immediately when you test on IE using this, instead of only going wrong in a particular case that, when it affects you, is likely to be extremely confusing and hard to debug.

Then again, you aren't currently using even multiple listeners for the same event, so you could easily get away with a old-fashioned DOM Level 0 event handler, something like:

window.onload= function() {
    var changed= false;
    document.getElementById('contactButton').onmouseover= function() {
        if (!changed) {
            alert('changing text');
        }
        changed= true;
    };
};
bobince
I think this is the first answer on SO that addresses the suckage of Resig's `addEvent` entry. There was a brief exchange in the comments of this answer: http://stackoverflow.com/questions/1466875/ies-understanding-of-this/1471006#1471006 where kangax enlightened me on the failures of relying on function decompilation/serialization for identifying a function object. Good write up. A++++++++
Crescent Fresh
+1 second that.
galambalazs