Skip to content

Instantly share code, notes, and snippets.

@JeffreyWay
Created January 18, 2012 21:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save JeffreyWay/1635889 to your computer and use it in GitHub Desktop.
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
Copy link

Couto commented Jan 18, 2012

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
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
Copy link

dhrrgn commented Jan 18, 2012

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
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
Copy link

Couto commented Jan 18, 2012

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
Copy link

dhrrgn commented Jan 18, 2012

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

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

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

@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

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

fn.call(el, window.event);

@eliperelman
Copy link

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

@JeffreyWay
Copy link
Author

@jeremy - Oh yeah, forgot about that.

@mattneary
Copy link

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

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
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