UNPKG

wuidom

Version:

WuiDom - Core Class for WizUI

242 lines (200 loc) 6.42 kB
/** * @module domEvents */ /** * Mouse event lock timer. Set to 0 when not locked. * * Note: This lock is required to overcome an issue with touch and mouse event compatibility across * browsers. The main issue being that when touch events are present the mouse events are fired * 300ms later. As such using just mouse events proves to be too slow on mobile devices. Further to * this when actually using the touch events, we would end up with double events, and what's worse * is that mouse events don't only occur 300ms later, but they also don't exactly fire on the DOM * where the touch event fired. But rather so, it would fire on any element which ends up there * after the touch event is processed. This resulted in ghost clicks on links etc, that would appear * after the touch event. * * References: * - http://www.html5rocks.com/en/mobile/touchandmouse/ * - https://developers.google.com/mobile/articles/fast_buttons * - https://github.com/Polymer/PointerEvents * - http://blogs.msdn.com/b/davrous/archive/2013/02/20/handling-touch-in-your-html5-apps-thanks-to-the-pointer-events-of-ie10-and-windows-8.aspx * * @type Number */ var mouseLock = 0; /** * Mouse event lock threshold * @type Number */ var mouseLockThreshold = 500; /** * Function which updates timestamp on mouse lock. This is used to determine if mouse events occur * within the locked threshold. */ function updateMouseLock() { mouseLock = Date.now(); } /** * Function which clears mouse events lock. */ function clearMouseLock() { mouseLock = 0; } /** * Function which checks if a mouse event occured within the mouse lock threshold. If so it will * return true. Otherwise it will return false. * * @returns {Boolean} */ function isMouseLocked() { return mouseLock !== 0 && (Date.now() - mouseLock) < mouseLockThreshold; } /** * DOM event prefix * @type String */ var domEventPrefix = 'dom'; /** * Function which created DOM event listeners according to the given wui-dom events. * @param {String} evt */ exports.new = function (evt) { var self = this; // Separate DOM event prefix from DOM event name var evtNameParts = evt.split('.'); // Ensure first part is in fact the prefix if (evtNameParts[0] !== domEventPrefix) { return; } // Check if DOM event name is valid and also make sure we are not already listening for // these DOM events var domEventName = evtNameParts[1]; if (!domEventName || this.domListeners[domEventName]) { return; } switch (domEventName) { case 'touchstart': // If this event is a touchstart event, attach mousedown compatibility bindings along // with the touchstart event var mouseDownFn = function (e) { if (isMouseLocked() || e.which !== 1) { return; } self.emit('dom.touchstart', e); }; var touchStartFn = function (e) { updateMouseLock(); self.emit('dom.touchstart', e); }; this.domListeners.touchstart = { mousedown: mouseDownFn, touchstart: touchStartFn }; this.rootElement.addEventListener('mousedown', mouseDownFn); this.rootElement.addEventListener('touchstart', touchStartFn); break; case 'touchmove': // If this event is a touchmove event, attach mousemove compatibility bindings along // with the touchmove event var mouseMoveFn = function (e) { if (mouseLock || e.which !== 1) { return; } self.emit('dom.touchmove', e); }; var touchMoveFn = function (e) { self.emit('dom.touchmove', e); }; this.domListeners.touchmove = { mousemove: mouseMoveFn, touchmove: touchMoveFn }; this.rootElement.addEventListener('mousemove', mouseMoveFn); this.rootElement.addEventListener('touchmove', touchMoveFn); break; case 'touchend': // If this event is a touchend event, attach mouseup compatibility bindings along with // the touchend event var mouseUpFn = function (e) { if (isMouseLocked() || e.which !== 1) { clearMouseLock(); return; } self.emit('dom.touchend', e); }; var touchEndFn = function (e) { updateMouseLock(); self.emit('dom.touchend', e); // This prevents the firing of mouse events after a touchend. // This fixes the issue with iframes, where by if an iframe falls under your pointer // after touchend is processed, but before the mouse events are, the mouse events are // fired inside the iframe placing them out of scope. This prevents us from intervening. e.preventDefault(); }; this.domListeners.touchend = { mouseup: mouseUpFn, touchend: touchEndFn }; this.rootElement.addEventListener('mouseup', mouseUpFn); this.rootElement.addEventListener('touchend', touchEndFn); break; default: // Otherwise the default is to bind event as is var defaultFn = function (e) { self.emit(evt, e); }; this.domListeners[domEventName] = defaultFn; this.rootElement.addEventListener(domEventName, defaultFn); break; } }; /** * Function which removes DOM event listeners for a given wui-dom event * @param {String} evt */ exports.remove = function (evt) { if (this.listeners(evt).length !== 0) { return; } var evtNameParts = evt.split('.'); if (evtNameParts[0] !== domEventPrefix) { return; } var domEventName = evtNameParts[1]; var domListener = this.domListeners[domEventName]; // Ensure dom event listener exists if (!domListener) { return; } // Destroy grouped event listeners if (domListener !== null && typeof domListener === 'object') { for (var eventName in domListener) { var evtFn = domListener[eventName]; this.rootElement.removeEventListener(eventName, evtFn); } delete this.domListeners[domEventName]; return; } // Default event listener destruction this.rootElement.removeEventListener(domEventName, domListener); delete this.domListeners[domEventName]; }; /** * Function which destroys all bound event listeners on the current wui-dom object */ exports.destroy = function () { for (var domEventName in this.domListeners) { var domListener = this.domListeners[domEventName]; // Destroy grouped event listeners if (domListener !== null && typeof domListener === 'object') { for (var eventName in domListener) { var evtFn = domListener[eventName]; this.rootElement.removeEventListener(eventName, evtFn); } continue; } // Default event listener destruction this.rootElement.removeEventListener(domEventName, domListener); } this.domListeners = {}; };