Closure event delegation - event listener on DOM parent that covers children/descendants of a given class

1.7k views Asked by At

In jQuery, you can do the following:

$('#j_unoffered').on('click', '.icon_del', function () {...

This puts one handler on the element j_unoffered that fires if any descendant element with class icon_del is clicked. It applies, furthermore, to any subsequently created icon_del element.

I can get this working fine in Closure where the click is on the element itself.

goog.events.listen(
    goog.dom.getElement('j_unoffered'),  
    goog.events.EventType.CLICK,
    function(e) {...

How can I specify a parent event target in Closure that works for its children/descendants in the same way as the jQuery example?

I'm assuming I need to use setParentEventTarget somehow, but I'm not sure how to implement it for DOM events. Most of the documentation I've found pertains to custom dispatch events.

-- UPDATE --

I'm wondering if there is anything wrong with this rather simple solution:

goog.events.listen(
    goog.dom.getElement('j_unoffered'),  
    goog.events.EventType.CLICK,
    function(e) {
        if (e.target.className.indexOf('icon_del') !== -1) {...

It still leaves this bound to the parent, but e.target allows a work-around. The fifth argument in listen (opt_handler) allows you to bind this to something else, so I guess that's an avenue, too.

2

There are 2 answers

7
Tony On BEST ANSWER

I don't know about such possibility too, so I suggest other piece of code:

var addHandler = function(containerSelector, eventType, nestSelector, handler) {
    var parent = goog.isString(containerSelector) ? 
                 document.querySelector(containerSelector) :
                 containerSelector;

    return goog.events.listen(
        parent,
        eventType,
        function(e) {

            var children = parent.querySelectorAll(nestSelector);
            var needChild = goog.array.find(children, function(child) {
                return goog.dom.contains(child, e.target);
            });

            if (needChild)
                handler.call(needChild, e);                

        });
});

Usage:

addHandler('#elem', goog.events.EventType.CLICK, '.sub-class', function(e) {
    console.log(e.target);
});

Update:

If you will use this e.target.className.indexOf('icon_del') there will be possibility to miss the right events. Consider a container div with id = container, it has couple of divs with class innerContainer, and each of them contains couple of divs with class = finalDiv. And consider you will add event handler with your code above, which will check e.target for innerContainer class. The problem is when user will click on finalDiv your handler will be called, but the event target will be finalDiv, which is not innerContainer, but contained by it. Your code will miss it, but it shouldn't. My code checks if e.target has nested class or contained by it, so you will not miss such events.

opt_handler can't really help you either, because there might be many nested elements you want to hanlde (which of them will you pass here? maybe all, but not that helpful, you can get them in event handler whenever you want), moreover they can be added dynamically after, so when you add handler you could not know about them.

In conclusion, I think doing such a job in an event handler is justified and most efficient.

3
MarianCJC On

What you are referring to is called event delegation

It seems that this is not possible (out of the box) with Google Closure Library; So my recommandation is to use jQuery or another similar event handling library that offers this functionality. If this is not possible or if you wanna do it by hand here's one possible approach (NOTE: this is not for production use)

var delegateClick = function(containerId, childrenClass, handler){
  goog.events.listen(goog.dom.getElement(containerId), goog.events.EventType.CLICK, function(event){
    var target = event.target;
    //console.log(event);
    while(target){
      if ( target.className && target.className.split(" ").indexOf(childrenClass)!== -1) {
        break;
      }
      target = target.parentNode;
    }
    if(target){
      //handle event if still have target
      handler.call(target, event);
    }
  });
}
//then use it, try this here: http://closure-library.googlecode.com/git/closure/goog/demos/index.html
//..select the nav context
delegateClick( 'demo-list' ,'goog-tree-icon', function(event){console.log(event);})

Here's a more in depth analysis of event delegation

Again, you should use a proven library for this, here are some options: jQuery, Zepto, Bean