Guidance needed for chriswessels:hammer (Hammer.js/Blaze Integration)


#1

Hey all,

I’m the author of the chriswessels:hammer package: https://atmospherejs.com/chriswessels/hammer

It adds the Template.foo.gestures function which allows you to register an event map of callbacks (much like Template.foo.events, but specifically for multi-touch gestures. It is powered by Hammer.js under the hood.

The first version created an instance of Hammer.js for each registered gesture and bound it to the DOM element in a data attribute (using the jQuery plugin for Hammer.js, which is quite clean).

I realised this wasn’t a great approach because Blaze could randomly replace the DOM element and thus the Hammer.js instance would be lost. I tried using the Template.foo.rendered (now onRendered) callback, but this didn’t seem to be triggered for every Blaze update, and the Hammer.js instance was still lost on occasion.

I then changed the structure of the plugin to create a single instance of Hammer.js on the body element (again, bound to the DOM element via a data attribute), and any gestures registered via the Template.foo.gestures plugin were registered with that the body instance (by computing a ultra-specific selector string from the body element down to the specific element in the event map and doing a match check when a tracked gesture was triggered on the body instance). This was working fine, but with Meteor 1.1 it seems to have broken!

Given that the package seems to be the most popular for integrating and using Hammer.js, I want to fix it properly. I’m therefore after guidance on to how to architect the package internals so that it plays well with Blaze internals. I’m open to any/all changes!

I guess the ideal architecture would be a single instance of Hammer.js attached to each template instance that makes a call to Template.foo.gestures, with the specific selectors in the event map being matched against in the callback (which then executes any matching callbacks specified). This seems like the right balance between memory usage and granularity of control.

The source code is here: https://github.com/chriswessels/meteor-hammer

Guidance anyone can provide would be much appreciated!

Kind regards,
Chris


#2

Hi Chris,

The idea of one Hammer instance for each template instance sounds right to me. This also has some other benefits:

  1. Properly teardown the Hammer instances in the onDestroyed callbacks of a template;
  2. Individually configure the Hammer instance for each template (e.g. recognizers to use) instead of relying on one single global configuration. Maybe you need an extra api for that (Template.my.configureHammer, or expose the hammer instance in the onCreated callback as this.hammer)
  3. No longer need to calculate a super specific selector, and the amount of selectors to match for each touch event would be smaller.

Hope this helps!
Evan


#3

Thanks for the reply @evanyou. I’ve had similar thoughts - I’m just after some guidance on implementation specifics from someone who has a good understanding of Blaze internals.

I’ll find some time for investigating this in the next few days.

Chris


#4

Hmm, so it seems that using a single instance of Hammer for the template will be a bit difficult, because there is no parent DOM element to which the instance can be attached.

Vote on your preferred approach please:

  1. I may revert back to the approach of having an instance of Hammer for each gesture (which is attached to the element(s) matching the gesture selector in the event map).

  2. Or, perhaps it would be feasible to use TemplateInstance.firstNode and TemplateInstance.lastNode to wrap the contents of the template in a wrapper element to which the Hammer instance could be attached.

  3. Or, I could introduce a Blaze block helper that wraps its inner-content in a wrapper div to which the Hammer.js instance is attached. You could even have multiple per template, and specify a set of gestures for each area.

Template:

<template name="foo">
<!-- Html outside touch area -->
<div class="something"></div>
{{#HammerTouchArea gestureMap="map1"}}
  <!-- Your html -->
  <div class="bar">
    <!-- ... -->
  </div>
{{/HammerTouchArea}}
</template>

Template JS:

Template.foo.gestures({
  map1: {
    'swipeleft .bar': function (event, template) {
      /* `event` is the Hammer.js event object */
      /* `template` is the `Blaze.TemplateInstance` */
      /* `this` is the data context of the element in your template */
    }
  }
});

What do you think?

Chris


#5

I’m no expert at Meteor, so please take whatever I say with a grain of salt, but the syntax specified in that Blaze template seems a bit unintuitive, and if you have a template with a lot of different interactions, seems like it could need quite a number of those tags. The #2 wrapper element idea sounds cool, though; any idea how much work that would be?


#6

can you just attach to TemplateInstance.firstNode.parentNode ?


#7

@aendrew: I understand your sentiment, but I think #3 is the best option at the moment. If you have multiple interactions in a single template, you’d only have one instance of HammerTouchArea, and attach multiple gestures to that instance. You’d only have multiple instances in a single template if you were doing something strange/special.

@xumx: Not really. As an example:

<template name="parent">
  <div id="parentNodeElement">
    {{> childOne }}
    {{> childTwo}}
  </div>
</template>
<template name="childOne">
  <div id="childOneFirstNode"></div>
  <div id="childOneLastNode"></div>
</template>
<template name="childTwo">
  <div id="childTwoFirstNode"></div>
  <div id="childTwoLastNode"></div>
</template>

If the childOne template had touch behavior enabled and the Hammer instance were attached to TemplateInstance.firstNode.parentNode, this would technically attach the Hammer instance to #parentNodeElement. This would then pollute touch behaviour into childTwo, so if a gesture was performed inside #childTwoFirstNode, the behaviour defined in childOne would trigger… Not a feasible solution!

The proposed solution #2 takes TemplateInstance.firstNode and TemplateInstance.lastNode, and wraps them and everything in between in an element which the Hammer instance is attached to. I don’t like this because it is ‘magic’ and it’s not immediately obvious how it works. It may also cause unexpected behaviour given the injection of an unknown element to the developer.

I like solution #3 because the wrapper element to which the Hammer instance is attached is explicitly defined by the developer, making it clear, easily controllable and predictable.

I think I’ll write a release candidate implementing solution #3 as soon as I get a moment - just super busy at the moment.

As always, I welcome any thoughts on the above…

Chris


#8

Hey everyone,

I’ve published 4.0.0-rc1 on Atmosphere. You can test it by running:

$ meteor add chriswessels:hammer@4.0.0-rc1

The new API is not backwards compatible. I need to write new documentation, but this should be enough to get you going: https://github.com/chriswessels/meteor-hammer/blob/rewrite/API.md

I apologise for the changes, but I’m happy with this new approach. It feels way less hacky than before.

Simple example of new API:

<template name="simple">
  {{#HammerTouchArea gestureMap=templateGestures}}
    <ul>
    {{#each someArray}}
      <li>
        {{someField}}
      </li>
    {{/each}}
    </ul>
  {{/HammerTouchArea}}
</template>
Template.simple.helpers({
  templateGestures: {
    'doubletap ul li': function (event, templateInstance) {
      /* `event` is the Hammer.js event object */
      /* `templateInstance` is the `Blaze.TemplateInstance` */
      /* `this` is the data context of the element in your template, so in this case someField from someArray */
    }
  }
});

The new API also provides a callback for configuring the Hammer instance as you’d like. It also allows you to specify initialisation options for Hammer (which let you set things like cssProps, which could previously not be done).

If I get the thumbs up from all of you, I’ll write more comprehensive docs and publish 4.0.0 as a major release on Atmosphere.

Cheers,
Chris


#9

I don’t like #2 for the same reason you mentioned.

Can we just use a class (or any data attribute) to label touch zones? if there are multiple of such areas in one template. It is still ok as long as the selector in event declaration is specific enough. This way there is no need to inject phantom wrapper elements.

<template name="foo">
  <!-- Html outside touch area -->
  <div class="something"></div>

  <div class="bar hammer-touch-area">
    <!-- ... -->
  </div>
</template>

#10

@xumx, I believe using a block template is a better solution. It allows hooking directly into the Blaze template lifecycle callbacks for managing the lifecycle of the Hammer.js instance. It’s also very efficient and gives the developer control of the number of instances in a template (or across their entire app), with complete separation between those instances (in terms of gestures, Hammer.Manager configuration and Hammer.Recongizers).

Thanks for your comments though - they’re appreciated. Have you given 4.0.0-rc1 a try yet?

Chris


#11

Hi everyone,

I’ve published 4.0.0 to Atmosphere. Please see README.md and MIGRATION.md if you need to migrate existing implementations to the new API.

Chris