-
-
Save JeffreyWay/1635889 to your computer and use it in GitHub Desktop.
// 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 | |
} | |
}); | |
} |
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.
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."
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));
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
}
});
}
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
}
});
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.
@jwmcpeak Also, e
is not available, so you need to normalize that as well:
var evt = e || window.event,
target = e.target || e.srcElement;
@eliperelman Not by default, but the addEvent() implementation above takes care of that.
fn.call(el, window.event);
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. :)
@JeffreyWay longer, but worth it: https://gist.github.com/1636094
@jeremy - Oh yeah, forgot about that.
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"));
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
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);
}
};
})();
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'