admin管理员组

文章数量:1315344

How would I do jQuery-style event delegation with plain JavaScript in Hammer.js? E.g.:

Hammer(document).on('tap', '.item', function () {
  console.log('tapped')
})

Is this directly possible or do I have to do the delegation myself?

How would I do jQuery-style event delegation with plain JavaScript in Hammer.js? E.g.:

Hammer(document).on('tap', '.item', function () {
  console.log('tapped')
})

Is this directly possible or do I have to do the delegation myself?

Share Improve this question asked Jul 26, 2013 at 11:15 SachaSacha 2,8343 gold badges23 silver badges27 bronze badges 2
  • Inspecting target - will only work if you click on element with .item. But if inside that .item there is another element and you click on that inner element - inspecting target will NOT help. you'd need to scan its parent (which is what other answers provided) - which exactly what you wanted in your question $.on('tap', '.item', function () {...} . with jquery - using your code in question , you will be provided with the right info EVEN if you clicked in an inner element to .item – Royi Namir Commented Apr 18, 2017 at 11:32
  • As you can see here - inspecting target will show you the button which is not .item jsbin./hobenunale/1/edit?html,js,output - while jquery does show you the delegeated element. – Royi Namir Commented Apr 18, 2017 at 11:44
Add a ment  | 

4 Answers 4

Reset to default 3

Inspired by Jools' answer, here is what I've e up with. I didn't bother with a pure-JS solution--in fact, this is intended for use with a Backbone.View, but is easily adaptable to other situations.

It will stop climbing the node tree when it reaches the View's root element (this.el). Starting from the target identified by the HammerJS event, it will seek the first element matching the specified selector, and return undefined if no such element is found.

To use in a non-Backbone environment, just pass in a reference to the limiting/containing element as a 3rd argument (as in Jools' solution) and use it to replace this.el.

/**
 * @param {Node}target - the target of the received event
 * @param {String}selector - any jQuery-patible selector
 */
getEventDelegate: function (target, selector) {
     var delegate;
     while (target && target != this.el) {
        delegate = $(target).filter(selector)[0];
        if (delegate) {
           return delegate;
        }
        target = target.parentNode;
     }
     return undefined;
  }

If you have an onTap callback function, it might look something like this:

/**
 * @param {Event}ev
 */
onTap: function (ev) {
   var target = this.getEventDelegate(ev.target, "#desiredTarget");
   if (target) {
      // TODO something with the target...
   }
}

And to hook it all up, you'd want something like this...

this._hammer = new Hammer(this.el);
this._hammer.on('tap', _.bind(this.onTap, this));

(and don't forget to call this._hammer.destroy() when your view is destroyed!)

I use the following function to test if the target is a delegate.

function _getDelegate(selector, target, currentTarget) {

   var delegate = null;

   while (target && target != currentTarget) {
      delegate = $(target).filter(selector)[0];
      if (delegate) 
         return delegate;
      target = target.parentNode;
   }

   return delegate;

}

I'm using the jquery filter because I use it a lot and with some more plex selectors.

Usage:

var delegationSelector = ".child";
$element.hammer()
$element.on("tap", function handler(event){
   var delegate = _getDelegate(delegationSelector, event.target, event.currentTarget);
   if(delegate){ // or if(!delegate) return;
      // Your handler code
   }
});

This won't work with all selectors, but I'd say it's better than André's in that it will still work if your "target" has child elements. E.g.

<div id="parent">
    <div class="child">
        <div class="text">Test</div>
    </div>
</div>

André's will fail here because target is the [div.text] and doesn't have your class.

For the above usage of _getDelegate, lets' say $element = $("#parent"). If you tapped the word "Test" event.target would be [div.text] (where you tapped) and event.currentTarget would be [div.parent] (where the handler is registered). When you called _getDelegate with these parameters, you would receive [div.child] and know that you should run your handler code.

For a pure JS version, you'd want something like this, where you loop up through the parents looking to see if you hit anything.

var delegate;
var target = e.target; // e.g. Inner div
var currentTarget = e.currentTarget // e.g. #parent

while(target && target != currentTarget){
   if(target.className.indexOf('child')!=-1){
      delegate = target;
      break;
   }
   target = target.parentNode;
}

if( delegate ) {
   console.log('delegated tap received');
}

There are still plenty of flaws with this. The whole indexof className is a bad idea because classes can be sub-strings of one another e.g. "myClass" and "myClass2". Also, my code makes the assumption that all of your subelements are positioned within their parents.

You simply need to inspect the target of the Event object that's passed into your handler.

Here's a demo.

Continuing @Lambarr's solution - Typescript:

private delegateBind(rootElement:Node,e, selector:string, handler: (target:any) => any)
  {

    let target=e.target;
    let o = {

      getEventDelegate: function ()
      {
        var delegate;

        while (target && target != rootElement)
        {
          delegate = jQuery(target).filter(selector)[0];
          if (delegate)
          {
            return delegate;
          }

          target = target.parentNode;
        }
        return undefined;
      }

    };

     target = o.getEventDelegate();
    if (target)
    {
      handler(target);
    }
  }

Usage :

private registerDocumentclick()
  {

     Hammer(this.document).on('tap', e =>
        this.delegateBind(this.document,e,'.article', (elm) => console.log(elm.classList))

    );
  }

本文标签: javascriptEvent delegation with HammerjsStack Overflow