API Docs for:
Show:

File: development/js/utils/events.js

import type from './type';
import objects from './object';

/**
 * Various event mappings
 *
 * @module Utils
 * @class Utils.Events
 * @static
 */


/**
 * Creates device-specific eventtypes
 *
 * @property eventType
 * @type {object}
 * @for Utils.Events
 *
 * @example
 *     import {eventType} from './utils/events';
 *
 *     eventType.DOWN; // -> mousedown/touchstart/pointerdown
 *     eventType.UP; // -> mouseup/touchend/pointerup
 *     eventType.MOVE; // -> mousemove/touchmove/pointermove
 *     eventType.CLICK; // -> click
 *     eventType.FOCUSIN; // -> focus
 *     eventType.FOCUSOUT; // -> blur
 *     eventType.OVER; // -> mouseover/mouseover/pointerover
 *     eventType.OUT; // -> mouseout/mouseout/pointerout
 *     eventType.SUBMIT; // -> submit
 *     eventType.RESET; // -> reset
 *     eventType.CHANGE; // -> change
 *     eventType.RESIZE; // -> resize/orientationchange/orientationchange
 *     eventType.TRANSIITON; // -> transitionend with vendor event names
 */
export const eventType = (() => {

    var mapping = {
            // initial mapping for mouse-devices
            DOWN: 'mousedown',
            MOVE: 'mousemove',
            UP: 'mouseup',
            CLICK: 'click',
            FOCUSIN: 'focus',
            FOCUSOUT: 'blur',
            OVER: 'mouseover',
            OUT: 'mouseout',
            ENTER: 'mouseenter',
            LEAVE: 'mouseleave',
            SUBMIT: 'submit',
            RESET: 'reset',
            CHANGE: 'change',
            RESIZE: 'resize',
            SCROLL: 'scroll',
            INPUT: 'keyup',
            TRANSITION: 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd'
        },
        pointers = [
            '-pointer-down',
            '-pointer-up',
            '-pointer-move',
            '-pointer-over',
            '-pointer-out',
            '-pointer-enter',
            '-pointer-leave'
        ],
        format;

    // modifies the given string, prepends it with b-arg
    format = function (a, b, toUpper) {
        return b + a.replace(/(-[a-z])/g, function (match) {
            return toUpper ?
                   match.charAt(1).toUpperCase() + match.slice(2) :
                   match.slice(1);
        });
    }

    if (navigator.pointerEnabled) {
        // pointer-enabled device
        return objects.extend(mapping, {
            DOWN: format(pointers[0], ''),
            MOVE: format(pointers[2], ''),
            UP: format(pointers[1], ''),
            OVER: format(pointers[3], ''),
            OUT: format(pointers[4], ''),
            ENTER: format(pointers[5], ''),
            LEAVE: format(pointers[6], ''),
            RESIZE: 'orientationchange resize'
        });
    }

    if (navigator.msPointerEnabled) {
        // ms-pointer-enabled device
        return objects.extend(mapping, {
            DOWN: format(pointers[0], 'MS', true),
            MOVE: format(pointers[2], 'MS', true),
            UP: format(pointers[1], 'MS', true),
            OVER: format(pointers[3], 'MS', true),
            OUT: format(pointers[4], 'MS', true),
            ENTER: 'mouseenter',
            LEAVE: 'mouseleave',
            RESIZE: 'orientationchange resize'
        });
    }

    if (document.ontouchstart !== undefined) {
        // touch-enabled device
        return objects.extend(mapping, {
            DOWN: 'touchstart',
            MOVE: 'touchmove',
            UP: 'touchend',
            RESIZE: 'orientationchange resize'
        });
    }

    // if not returned until now, assume mouse-device
    return mapping;

})();



// Helpers
const Swipe = {
    current: {
        startTime: null, // timestamp when swiping has started
        endTime: null, // timestamp when swiping has stopped
        duration: null, // absolute duration of the swipe
        startPointX: null, // x-coordinate where swipe has started
        endPointX: null, // x-coordinate where swipe has ended
        startPointY: null, // y-coordinate where swipe has started
        endPointY: null, // y-coordinate where swipe has ended
        distanceX: 0, // absolute value of  distance on x-axis in px
        distanceY: 0, // absolute value of  distance on y-axis in px
        directionX: null, // -1 => left, 1 => right, 0 => not
        directionY: null // 1 => down, -1 => up, 0 => not
    },
    trackStart: function (obj) {
        Swipe.current.startTime = +new Date();
        Swipe.current.startPointX = obj.pageX;
        Swipe.current.startPointY = obj.pageY;
    },
    trackMove: function (obj) {
        var cur = Swipe.current;

        cur.directionX = Swipe.verticalDir(obj.pageX);
        cur.directionY = Swipe.horizontalDir(obj.pageY);
        cur.endTime = +new Date();
        cur.duration = cur.endTime - cur.startTime;
        cur.endPointX = obj.pageX;
        cur.endPointY = obj.pageY;
        cur.distanceX = Math.abs(cur.endPointX - cur.startPointX);
        cur.distanceY = Math.abs(cur.endPointY - cur.startPointY);

        return objects.inherit(obj, cur);
    },
    trackEnd: function (obj) {
        var cur = Swipe.current;
        cur.endTime = +new Date();
        cur.duration = cur.endTime - cur.startTime;
        cur.endPointX = obj.pageX;
        cur.endPointY = obj.pageY;
        cur.distanceX = Math.abs(cur.endPointX - cur.startPointX);
        cur.distanceY = Math.abs(cur.endPointY - cur.startPointY);

        // reset the current swipe values back to initial state
        Swipe.current = {
            startTime: null,
            endTime: null,
            duration: null,
            startPointX: null,
            endPointX: null,
            startPointY: null,
            endPointY: null,
            distanceX: 0,
            distanceY: 0,
            directionX: null,
            directionY: null
        };

        return objects.inherit(obj, cur);
    },
    unify: function (obj) {
        // takes the eventobject (generic) and unifies it beween
        //   different implementations (mouse/touch/pointer events)
        var oEvent = obj.originalEvent,
            event = objects.extend({}, obj);

        if (!type.isNil(oEvent.touches)) {
            // touch driven device
            if (!type.isNil(oEvent.changedTouches) && oEvent.changedTouches.length > 0) {
                // iOS driven, where changedTouches have highest precedence
                oEvent = oEvent.changedTouches[0];

            } else if (!type.isNil(oEvent.targetTouches) && oEvent.targetTouches.length > 0) {
                // iOS driven, targetTouches have second highest precedence
                oEvent = oEvent.targetTouches[0];

            } else {
                // android device or target/changedTouches is empty
                oEvent = oEvent.touches[0];
            }
        }

        // pointer-devices and mouse-devices do not have the nonsense of different
        //   types of touches and we can safely use the oEvent data
        event.pageX = oEvent.pageX;
        event.pageY = oEvent.pageY;
        event.preventDefault = obj.preventDefault.bind(obj);
        event.stopPropagation = obj.stopPropagation.bind(obj);
        event.stopImmediatePropagation = obj.stopImmediatePropagation.bind(obj);

        return event;
    },
    verticalDir: function (xPos) {
        var startPoint = Swipe.current.startPointX;

        return xPos < startPoint ? 'left' :
               xPos > startPoint ? 'right' :
               'unmoved';
    },
    horizontalDir: function (yPos) {
        var startPoint = Swipe.current.startPointY;

        return yPos < startPoint ? 'up' :
               yPos > startPoint ? 'down' :
               'unmoved';
    },
    setTouchAction: function (nodeRef) {
        // adds the css "touch-action" property to ms-pointer using
        //   devices since they otherwise show strange behaviour and
        //   touch-action events are only supported by IE 10+
        //
        nodeRef.css({
            WebkitTouchAction: 'pan-y',
            MozTouchAction: 'pan-y',
            msTouchAction: 'pan-y',
            touchAction: 'pan-y'
        });
    }
};

// API
export const onSwipe = (nodeRef, func) => {
    var $node = $(nodeRef);

    // add a listener to start listening on mousedown/touchstart/pointerdown
    $node.on(eventType.DOWN, function (event) {
        Swipe.trackStart(Swipe.unify(event));
    });

    // add a listener which updates on swipe movements for mousemove/touchmove/pointermove
    $node.on(eventType.MOVE, function (event) {
        if (Swipe.current.startTime !== null) {
            func.call(this, Swipe.trackMove(Swipe.unify(event)));
        }
    });

    // register a listener which fires the given function with a unifyd event object
    //   as argument on the node for mouseup/touchend/pointerup events
    $node.on(eventType.UP, function (event) {
        if (Swipe.current.startTime !== null) {
            func.call(this, Swipe.trackEnd(Swipe.unify(event)));
        }
    });

    Swipe.setTouchAction($node);

    // return a "release" function which removes the swipe behaviour again
    return function () {
        $node.off(eventType.DOWN);
        $node.off(eventType.MOVE);
        $node.off(eventType.UP);
    }
}

export const isMultiTouch = (() => {
    var _isMultiTouch = false;
    if (navigator.pointerEnabled || navigator.msPointerEnabled) {
        _isMultiTouch = (navigator.maxTouchPoints || navigator.msMaxTouchPoints) > 1;

    } else if (document.ontouchstart) {
        _isMultiTouch = true;
    }

    return () => _isMultiTouch;
})();



export default {
    eventType, onSwipe, isMultiTouch
}