Search code examples
javascriptjqueryevent-handlinggoogle-closureevent-delegation

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


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.


Solution

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