'use strict'; (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) window.propagating = factory(); } }(function () { var _firstTarget = null; // singleton, will contain the target element where the touch event started /** * Extend an Hammer.js instance with event propagation. * * Features: * - Events emitted by hammer will propagate in order from child to parent * elements. * - Events are extended with a function `event.stopPropagation()` to stop * propagation to parent elements. * - An option `preventDefault` to stop all default browser behavior. * * Usage: * var hammer = propagatingHammer(new Hammer(element)); * var hammer = propagatingHammer(new Hammer(element), {preventDefault: true}); * * @param {Hammer.Manager} hammer An hammer instance. * @param {Object} [options] Available options: * - `preventDefault: true | false | 'mouse' | 'touch' | 'pen'`. * Enforce preventing the default browser behavior. * Cannot be set to `false`. * @return {Hammer.Manager} Returns the same hammer instance with extended * functionality */ return function propagating(hammer, options) { var _options = options || { preventDefault: false }; if (hammer.Manager) { // This looks like the Hammer constructor. // Overload the constructors with our own. var Hammer = hammer; var PropagatingHammer = function(element, options) { var o = Object.create(_options); if (options) Hammer.assign(o, options); return propagating(new Hammer(element, o), o); }; Hammer.assign(PropagatingHammer, Hammer); PropagatingHammer.Manager = function (element, options) { var o = Object.create(_options); if (options) Hammer.assign(o, options); return propagating(new Hammer.Manager(element, o), o); }; return PropagatingHammer; } // create a wrapper object which will override the functions // `on`, `off`, `destroy`, and `emit` of the hammer instance var wrapper = Object.create(hammer); // attach to DOM element var element = hammer.element; if(!element.hammer) element.hammer = []; element.hammer.push(wrapper); // register an event to catch the start of a gesture and store the // target in a singleton hammer.on('hammer.input', function (event) { if (_options.preventDefault === true || (_options.preventDefault === event.pointerType)) { event.preventDefault(); } if (event.isFirst) { _firstTarget = event.target; } }); /** @type {Object.>} */ wrapper._handlers = {}; /** * Register a handler for one or multiple events * @param {String} events A space separated string with events * @param {function} handler A callback function, called as handler(event) * @returns {Hammer.Manager} Returns the hammer instance */ wrapper.on = function (events, handler) { // register the handler split(events).forEach(function (event) { var _handlers = wrapper._handlers[event]; if (!_handlers) { wrapper._handlers[event] = _handlers = []; // register the static, propagated handler hammer.on(event, propagatedHandler); } _handlers.push(handler); }); return wrapper; }; /** * Unregister a handler for one or multiple events * @param {String} events A space separated string with events * @param {function} [handler] Optional. The registered handler. If not * provided, all handlers for given events * are removed. * @returns {Hammer.Manager} Returns the hammer instance */ wrapper.off = function (events, handler) { // unregister the handler split(events).forEach(function (event) { var _handlers = wrapper._handlers[event]; if (_handlers) { _handlers = handler ? _handlers.filter(function (h) { return h !== handler; }) : []; if (_handlers.length > 0) { wrapper._handlers[event] = _handlers; } else { // remove static, propagated handler hammer.off(event, propagatedHandler); delete wrapper._handlers[event]; } } }); return wrapper; }; /** * Emit to the event listeners * @param {string} eventType * @param {Event} event */ wrapper.emit = function(eventType, event) { _firstTarget = event.target; hammer.emit(eventType, event); }; wrapper.destroy = function () { // Detach from DOM element var hammers = hammer.element.hammer; var idx = hammers.indexOf(wrapper); if(idx !== -1) hammers.splice(idx,1); if(!hammers.length) delete hammer.element.hammer; // clear all handlers wrapper._handlers = {}; // call original hammer destroy hammer.destroy(); }; // split a string with space separated words function split(events) { return events.match(/[^ ]+/g); } /** * A static event handler, applying event propagation. * @param {Object} event */ function propagatedHandler(event) { // let only a single hammer instance handle this event if (event.type !== 'hammer.input') { // it is possible that the same srcEvent is used with multiple hammer events, // we keep track on which events are handled in an object _handled if (!event.srcEvent._handled) { event.srcEvent._handled = {}; } if (event.srcEvent._handled[event.type]) { return; } else { event.srcEvent._handled[event.type] = true; } } // attach a stopPropagation function to the event var stopped = false; event.stopPropagation = function () { stopped = true; }; //wrap the srcEvent's stopPropagation to also stop hammer propagation: var srcStop = event.srcEvent.stopPropagation.bind(event.srcEvent); if(typeof srcStop == "function") { event.srcEvent.stopPropagation = function(){ srcStop(); event.stopPropagation(); } } // attach firstTarget property to the event event.firstTarget = _firstTarget; // propagate over all elements (until stopped) var elem = _firstTarget; while (elem && !stopped) { var elemHammer = elem.hammer; if(elemHammer){ var _handlers; for(var k = 0; k < elemHammer.length; k++){ _handlers = elemHammer[k]._handlers[event.type]; if(_handlers) for (var i = 0; i < _handlers.length && !stopped; i++) { _handlers[i](event); } } } elem = elem.parentNode; } } return wrapper; }; }));