1
\$\begingroup\$

I am currently learning JavaScript and created this cross-browser event utility as a learning exercise. It was not meant to be used in production code, and I just tried to cover only the cases that I'm aware of, so there might be a lot of edge cases that are not covered.

I'd be happy if you could check it and let me know how it looks and give me suggestions on how to improve it:

var helpers = {
    getOldEventName: function(eventName) {
        if (eventName.lastIndexOf('on', 0) !== 0) {
            return 'on' + eventName;
        }

        return eventName;
    },
    getModernEventName: function(eventName) {
        if (eventName.lastIndexOf('on', 0) !== -1) {
            return eventName.substring(2);
        }

        return eventName;
    },
    isCollection: function(variable) {
        return variable instanceof Array || variable instanceof HTMLCollection || variable instanceof NodeList;
    }
}

var myEvent = {
    addListener: function(element, eventName, callback) {
        var elements = element;
        if (!helpers.isCollection(element))
            elements = [element];
        for (var i = 0; i < elements.length; i++) {
            if (elements[i].addEventListener) {
                elements[i].addEventListener(helpers.getModernEventName(eventName), callback, false);
            } else if (element.attachEvent) {
                elements[i].attachEvent(helpers.getOldEventName(eventName), callback);
            } else {
                elements[i][helpers.getOldEventName(eventName)] = callback;
            }
        }
    },
    removeListener: function(element, eventName, callback) {
        var elements = element;
        if (!helpers.isCollection(element))
            elements = [element];
        for (var i = 0; i < elements.length; i++) {
            if (elements[i].removeEventListener) {
                elements[i].removeEventListener(helpers.getModernEventName(eventName), callback, false);
            } else if (elements[i].detachEvent) {
                elements[i].detachEvent(helpers.getOldEventName(eventName), callback);
            } else {
                elements[i][helpers.getOldEventName(eventName)] = null;
            }
        }
    },
    getEvent: function(event) {
        return event || window.event;
    },
    getTarget: function(event) {
        return (typeof event.target !== 'undefined') ? event.target : event.srcElement;
    },
    stopPropagation: function(event) {
        if (event.stopPropagation) {
            event.stopPropagation();
        } else if (typeof event.cancelBubble !== 'undefined') {
            event.cancelBubble = true;
        }
    },
    preventDefault: function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else if (typeof event.returnValue !== 'undefined') {
            event.returnValue = false;
        }
    }
}

Usage examples:

function myCallback(e) {
    e = myEvent.getEvent(e);
    alert(myEvent.getTarget(e).href);
    myEvent.stopPropagation(e);
    myEvent.preventDefault(e);
}
myEvent.addListener(document.links, 'click', myCallback);

function dynamicDivHandler(e) {
    e = myEvent.getEvent(e);
    var keyCode = e.keyCode;
    var node = document.getElementById('dynamicDiv');

    switch (keyCode) {
        case 37:
            node.style.left = (parseInt(node.style.left) - 5) + 'px';
            break;
        case 38:
            node.style.top = (parseInt(node.style.top) - 5) + 'px';
            break;
        case 39:
            node.style.left = (parseInt(node.style.left) + 5) + 'px';
            break;
        case 40:
            node.style.top = (parseInt(node.style.top) + 5) + 'px';
            break;
    }
}
myEvent.addListener(document, 'keydown', dynamicDivHandler);

P.S.: This exercise is from the book "Object-Oriented JavaScript" by Stoyan Stefanov.

\$\endgroup\$
2
\$\begingroup\$

Your code looks ok. Nothing really jumps out at me except these two things:

  1. Event handlers attached via attachEvent are executed in random order (Source: http://msdn.microsoft.com/en-us/library/ie/ms536343%28v=vs.85%29.aspx). You might want to create a shim to get older versions of Internet Explorer to execute event handlers in the order they were attached, which is something I believe jQuery does (or did).

  2. Create a function that patches the event object for cross browser compatibility, so your event handlers do not have to constantly call myEvent.getEvent(e). This will prevent repeated code in your handlers.

You could create separate functions to attach events in the three different ways, and use feature detection to determine which one to use:

var myEvent = {
    init: function() {
        if (document.addEventListener) {
            this.addListener = this.addListenerStandard;
            this.removeListener = this.removeListenerStandard;
        }
        else if (document.attachEvent) {
            this.addListener = this.addListenerMSIE;
            this.removeListener = this.removeListenerMSIE;
        }
        else {
            this.addListener = this.addListenerOldSkool;
            this.removeListener = this.removeListenerOldSkool;
        }
    },

    listen: function(elements, event, handler) {
        elements = !helpers.isCollection(elements) ? [elements] : elements;

        for (var i = 0; i < elements.length; i++) {
            this.addListener(elements[i], event, handler);
        }
    },

    stopListening: function(elements, event, handler) {
        elements = !helpers.isCollection(elements) ? [elements] : elements;

        for (var i = 0; i < elements.length; i++) {
            this.removeListener(elements[i], event, handler);
        }
    },

    addListenerStandard: function(element, event, handler) {
        element.addEventListener(event, handler, false);
    },

    addListenerMSIE: function(element, event, handler) {
        element.attachEvent("on" + event, handler);
    },

    addListenerOldSkool: function(element, event, handler) {
        element["on" + event] = handler;
    },

    removeListenerStandard: function(...) { ... },

    removeListenerMSIE: function (...) { ... },

    addListenerOldSkool: function(...) { ... },

    addListener: null,

    removeListener: null
};

myEvent.init();

That way you won't be detecting the proper DOM method on each iteration of your loop.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.