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

2

7

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.

Longheaded answered 13/12, 2013 at 23:53 Comment(0)
I
5

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.

Inglis answered 20/12, 2013 at 11:13 Comment(7)
+1 for a good start, but I'm confident that Google staff must have encountered this before, so I'm relucant to reinvent the wheel. See my update, too.Longheaded
@Longheaded let's see some demo: jsfiddle.net/g3Vj6/1 (I had to change code a little, because there is no closure in jsfiddle, but approach is the same). Try click defferent items and see what happens. I guess the second approach is more correct.Inglis
Yes it works, but as I understand you need more generic approach. I removed on calls from demo and used addEventListener. It works the same: jsfiddle.net/g3Vj6/2. I believe you need dom traversal here as is in my code.Inglis
The reason it doesn't recognise the span is because the e.target is the a!! Try console.log(e.target); :)Longheaded
I know that! This is exactly I wanted to demonstrate. You asked what's wrong with this simple solution, I demonstated that in some cases it will not work and code need to be more complex, isn't it what you wanted?Inglis
My apologies - I read your update insufficiently carefully. I see now that the issue is not that the handler's reach doesn't go deep enough (as I erroneously misread) but that it fails to recognise the appropriate class being on elements at the intermediate levels. My test case has the class on the deepest level, so it works fine, but I recognise the theoretical problem now. All that said, I still find it hard to believe that Closure requires you to create this logic yourself. Surely there must be an inbuilt method to do it?!! It's hardly an ususual situation to tackle.Longheaded
I use google closure too and I was surprised too, but it's seems there no inbuilt methods for this. You are not the first who asked this: #11518715 groups.google.com/forum/#!topic/closure-library-discuss/… And there is no answer. Closure uses other paradigm and moreover in any cases it's possible to find the way how to use direct event hadling rather event delegation (attach them in enterDocument). As I said before Closure uses other paradigm.Inglis
D
3

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

Dildo answered 20/12, 2013 at 9:44 Comment(3)
Thanks for the event delegation terminology. I use it; I just couldn't remember what it was called :) I think it's a long bow to suggest that Closure isn't a "proven library". +1 for a good start, though. See my update, too.Longheaded
@Longheaded I was suggesting a proven library (any) for event handling/delegation, as opposed to do your own. I was not suggesting that Closure isn't a good lib. Regards :)Dildo
Hello again. Why do you say "this is not for production use"?Longheaded

© 2022 - 2024 — McMap. All rights reserved.