Event delegation with Hammer.js

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?

Answers:

Answer

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

Here's a demo.

Answer

Inspired by Jools' answer, here is what I've come 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-compatible 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!)

Answer

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

Answer

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

    );
  }

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.