Skip to content

Instantly share code, notes, and snippets.

@JeffreyWay
Created January 18, 2012 21:39
Show Gist options
  • Select an option

  • Save JeffreyWay/1635889 to your computer and use it in GitHub Desktop.

Select an option

Save JeffreyWay/1635889 to your computer and use it in GitHub Desktop.
Legacy JS
// THE GOAL
// Write $('ul').on('click', 'a', fn); in JavaScript
// Must support old IE (so no Selectors API (matchesSelector) or anything)
// Can you shorten this?
var addEvent = (function () {
if (window.addEventListener) {
return function (el, ev, fn) {
el.addEventListener(ev, fn, false);
};
} else {
return function (el, ev, fn) {
el.attachEvent('on' + ev, function() {
return fn.call(el, window.event);
});
};
}
}());
var uls = document.getElementsByTagName('ul');
for ( var i = 0, len = uls.length; i < len; i++ ) {
addEvent(uls[i], 'click', function(e) {
var target = e.target || e.srcElement;
if ( target && target.nodeName.toLowerCase() === 'a' ) {
// proceed
}
});
}
@couto

couto commented Jan 18, 2012

Copy link
Copy Markdown

Without thinking too much

var addEvent = (function () {
    if (window.addEventListener) {
         return function (el, ev, fn) {
             el.addEventListener(ev, fn, false);
         };
    } 

    return function (el, ev, fn) {
         el.attachEvent('on' + ev, function() {
                return fn.call(el, window.event);
         });
    };
 }());

var uls = document.getElementsByTagName('ul'),
    i = uls.length - 1;

for (; i >= 0; i--) {
    addEvent(uls[i], 'click', function(e) {
    if ( e.target && e.target.nodeName.toLowerCase() === 'a' ) {
        // proceed
    }
});
}

@JeffreyWay

Copy link
Copy Markdown
Author

Nice. You got the two main ones I was looking for. I guess, if you wanted, you could also remove the toLowerCase(), and instead check for nodeName === 'A'

@dhrrgn

dhrrgn commented Jan 18, 2012

Copy link
Copy Markdown

Technically not even the amount of code you have is sufficient to fully duplicate what the one line of jQuery does. In IE (attachEvent) events always bubble and this references the window object, jQuery uses tricks and such to help/prevent these issues, which you would have to account for in your code.

@JeffreyWay

Copy link
Copy Markdown
Author

Yeah - it's not an attempt to reproduce jQuery's conveniences. More of a quick, "this is how you would do it with legacy, vanilla JS."

@couto

couto commented Jan 18, 2012

Copy link
Copy Markdown

I don't have IE here to test, but I think that e.target is always granted, so we dont't need that verification, please correct me if i'm wrong... .
We can easily replace the addEvent's if with a ternary operator and we can always bring some local variables to shorten our code a bit more…

(function(w, d) {
    var addEvent = (function(list) {
        return (list) ?
            function(el, ev, fn) {
                el.addEventListener(ev, fn, false);
            } : 
            function(el, ev, fn) {
                el.attachEvent('on' + ev, function() {
                    return fn.call(el, w.event);
                });
            }
    }(d.addEventListener));

    var uls = d.getElementsByTagName('ul'), 
        i = uls.length - 1;

    for (; i >= 0; i--) {
        addEvent(uls[i], 'click', function(e) {
            if (e.target.nodeName === 'A') {
                // proceed
            }
        });
    }
}(window, document));

@dhrrgn

dhrrgn commented Jan 18, 2012

Copy link
Copy Markdown

Can get a lot shorter written in CoffeeScript :P

addEvent = (->
  if window.addEventListener
    return (el, ev, fn) ->
      el.addEventListener ev, fn, false

  (el, ev, fn) ->
    el.attachEvent "on#{ev}", ->
      fn.call el, window.event
)()

for ul in document.getElementsByTagName 'ul'
  addEvent ul, 'click', (e) ->
    if e.target and e.target.nodeName.toLowerCase() == 'a'
      #proceed

(Note: Compilation will Fail with not logic actually inside the final if statement in the loop)

if you are curious, here is what that compiles into (used the -b switch on compilation)

var addEvent, ul, _i, _len, _ref;
addEvent = (function() {
  if (window.addEventListener) {
    return function(el, ev, fn) {
      return el.addEventListener(ev, fn, false);
    };
  }
  return function(el, ev, fn) {
    return el.attachEvent("on" + ev, function() {
      return fn.call(el, window.event);
    });
  };
})();
_ref = document.getElementsByTagName('ul');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  ul = _ref[_i];
  addEvent(ul, 'click', function(e) {
    if (e.target && e.target.nodeName.toLowerCase() === 'a') {
      // proceed
    }
  });
}

@jwmcpeak

Copy link
Copy Markdown

The target property doesn't exist in legacy IE's event model. So the handler would need to be modified to take that into account. Quick example:

addEvent(uls[i], 'click', function(e) {
    var target = e.target || e.srcElement;
    if (target.nodeName === 'A') {
        // proceed
    }
});

@eliperelman

Copy link
Copy Markdown

It's a tad bigger, but the API is nice, should support IE, and chaining on calls: https://gist.github.com/1636094 . Consuming code will benefit and shrink the more you use it as you won't have to fetch the elements and loop and do the attaching every time.

@eliperelman

Copy link
Copy Markdown

@jwmcpeak Also, e is not available, so you need to normalize that as well:

var evt = e || window.event,
    target = e.target || e.srcElement;

@jwmcpeak

Copy link
Copy Markdown

@eliperelman Not by default, but the addEvent() implementation above takes care of that.

fn.call(el, window.event);

@eliperelman

Copy link
Copy Markdown

You are correct. My fork operates differently, so I didn't take that into consideration. Also the variable I commented before is the wrong one. :)

@eliperelman

Copy link
Copy Markdown

@JeffreyWay

Copy link
Copy Markdown
Author

@jeremy - Oh yeah, forgot about that.

@mattneary

Copy link
Copy Markdown

Tweet sized addEvent function:

a=(function(a,b)function(c,d,e)a[b]?c[b].call(c,d,e):c.attachEvent("on"+d,function()e.call(c,a.event)))(window,"addEventListener")

Note that JavaScript 1.8 is required for the function shorthand. Here it is unminified

var addEvent = (function (w,ael) {
    return function (el, ev, fn) {
        w[ael] ? el[ael].call(el, ev, fn) :     
        el.attachEvent('on' + ev, function() {
            return fn.call(el, w.event);
        });
    };
}(window,"addEventListener"));

@heavensloop

Copy link
Copy Markdown

This might also work fine:

 1 var addEvent = addEV;
 2 function addEV(){
 3     return function (el, ev, fn) {
 4         if (window.addEventListener)
 5             el.addEventListener(ev, fn, false);
 6         else{
 7             el.attachEvent('on' + ev, function() {
 8                 return fn.call(el, window.event);
 9             });
10         }
11      };
12 }
13 var uls = document.getElementsByTagName('ul');
14 
15 for ( var i = 0, len = uls.length; i < len; i++ ) {
16     addEvent(uls[i], 'click', function(e) {
17         if ( e.target && e.target.nodeName.toLowerCase() === 'a' ) {
18             // proceed
19         }
20     });
21 }
22 

@JeffreyWay

Copy link
Copy Markdown
Author

Or, if we need multiple element event binding...

var addEvent = (function () {
var filter = function(el, type, fn) {
    for ( var i = 0, len = el.length; i < len; i++ ) {
        addEvent(el[i], type, fn);
    }
};
if ( document.addEventListener ) {
    return function (el, type, fn) {
        if ( el && el.nodeName || el === window ) {
            el.addEventListener(type, fn, false);
        } else if (el && el.length) {
            filter(el, type, fn);
        }
    };
}

return function (el, type, fn) {
    if ( el && el.nodeName || el === window ) {
        el.attachEvent('on' + type, function () { return fn.call(el, window.event); });
    } else if ( el && el.length ) {
        filter(el, type, fn);
    }
};

})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment