From f6b91c33e32b96b9240decae77632578749570cd Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Mon, 26 Aug 2013 15:53:57 -0500 Subject: [PATCH] Moving to gesture-based event handling --- example/adams/events.html | 22 +++ example/events.html | 2 + example/events.js | 7 + js/framework/framework-events.js | 234 +++++++++++-------------- js/framework/framework-gestures.js | 267 +++++++++++++++++++++++++++++ 5 files changed, 402 insertions(+), 130 deletions(-) create mode 100644 example/adams/events.html create mode 100644 js/framework/framework-gestures.js diff --git a/example/adams/events.html b/example/adams/events.html new file mode 100644 index 0000000000..1d989cc53c --- /dev/null +++ b/example/adams/events.html @@ -0,0 +1,22 @@ + + + + Example + + + + + + + + +
+ Tap me! + Swipe me! +
+
+ + + + diff --git a/example/events.html b/example/events.html index b6f2538731..4764f9411b 100644 --- a/example/events.html +++ b/example/events.html @@ -13,8 +13,10 @@
Tap me! + Swipe me!
+ diff --git a/example/events.js b/example/events.js index 151b500fc2..9b3e7739ef 100644 --- a/example/events.js +++ b/example/events.js @@ -12,3 +12,10 @@ window.FM.on('tap', function(e) { event: e }); }); +window.FM.on('touch', function(e) { + console.log('GOT TOUCH', e); + logEvent({ + type: 'touch', + event: e + }); +}); diff --git a/js/framework/framework-events.js b/js/framework/framework-events.js index 2c907208be..be72221fa6 100644 --- a/js/framework/framework-events.js +++ b/js/framework/framework-events.js @@ -11,150 +11,124 @@ */ (function(window, document, framework) { - framework.EventController = function (){}; - - // A map of event types that we virtually detect and emit - framework.EventController.prototype.VIRTUAL_EVENT_TYPES = ['tap', 'swipeleft', 'swiperight']; + framework.EventController = { + // A map of event types that we virtually detect and emit + VIRTUAL_EVENT_TYPES: ['tap', 'swipeleft', 'swiperight'], - /** - * Trigger a new event. - */ - framework.EventController.prototype.trigger = function(eventType, data) { - // TODO: Do we need to use the old-school createEvent stuff? + /** + * Trigger a new event. + */ + trigger: function(eventType, data) { + // TODO: Do we need to use the old-school createEvent stuff? - window.dispatchEvent(new CustomEvent(eventType, data)); - }; + window.dispatchEvent(new CustomEvent(eventType, data)); + }, - /** - * Shorthand for binding a new event listener to the given - * event type. - */ - framework.EventController.prototype.on = function(type, callback, element) { - var i; - var e = element || window; - /* - var virtualTypes = framework.EventController.VIRTUAL_EVENT_TYPES; + /** + * Shorthand for binding a new event listener to the given + * event type. + */ + on: function(type, callback, element) { + var i; + var e = element || window; + /* + var virtualTypes = framework.EventController.VIRTUAL_EVENT_TYPES; - for(i = 0; i < virtualTypes.length; i++) { - if(type.toLowerCase() == virtualTypes[i]) { - // TODO: listen for virtual event + for(i = 0; i < virtualTypes.length; i++) { + if(type.toLowerCase() == virtualTypes[i]) { + // TODO: listen for virtual event + return; + } + } + */ + + // Native listener + e.addEventListener(type, callback); + }, + + + /** + * Process a touchstart event. + */ + handleTouchStart: function(e) { + console.log("EVENT: touchstart", e); + framework.GestureController.detectGesture(e); + }, + + /** + * Process a touchmove event. + */ + handleTouchMove: function(e) { + console.log("EVENT: touchmove", e); + framework.GestureController.detectGesture(e); + + }, + + /** + * Process a touchend event. + */ + handleTouchEnd: function(e) { + console.log("EVENT: touchend", e); + framework.GestureController.detectGesture(e); + }, + + + /** + * Process a touchcancel event. + */ + handleTouchCancel: function(e) { + this._hasMoved = false; + this._touchStartX = null; + this._touchStartY = null; + }, + + // With a click event, we need to check the target + // and if it's an internal target that doesn't want + // a click, cancel it + handleClick: function(e) { + var target = e.target; + + if ( + ! target + || e.which > 1 + || e.metaKey + || e.ctrlKey + //|| isScrolling + || location.protocol !== target.protocol + || location.host !== target.host + // Not sure abotu this one + //|| !target.hash && /#/.test(target.href) + || target.hash && target.href.replace(target.hash, '') === location.href.replace(location.hash, '') + //|| target.getAttribute('data-ignore') == 'push' + ) { + // Allow it + console.log("EVENT: click", e); return; } - } - */ + // We need to cancel this one + e.preventDefault(); - // Native listener - e.addEventListener(type, callback); - }; - - - /** - * Process a touchstart event. - */ - framework.EventController.prototype.handleTouchStart = function(e) { - console.log("EVENT: touchstart", e); - if(e.type === 'touchstart') { - // We are now touching - this._isTouching = true; - } + // TODO: should we do this? + // e.stopPropagation(); + }, - // Reset the movement indicator - this._hasMoved = false; - - // Store touch coords - this._touchStartX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX; - this._touchStartY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY; + handlePopState: function(event) { + console.log("EVENT: popstate", event); + }, }; - - /** - * Process a touchmove event. - */ - framework.EventController.prototype.handleTouchMove = function(e) { - console.log("EVENT: touchmove", e); - - var x = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX, - y = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY; - - // Check if the finger moved more than 10px, and then indicate we should cancel the tap - if (Math.abs(x - this._touchStartX) > 10 || Math.abs(y - this._touchStartY) > 10) { - this._hasMoved = true; - console.log('DID MOVE'); - } - }; - - /** - * Process a touchend event. - */ - framework.EventController.prototype.handleTouchEnd = function(e) { - console.log("EVENT: touchend", e); - - if(this._isTouching && !this._hasMoved) { - console.log("EVENT: (virtual) tap", event); - framework.trigger('tap', { - bubbles: true, - cancelable: true - }); - } - framework.EventController._isTouching = false; - }; - - - /** - * Process a touchcancel event. - */ - framework.EventController.prototype.handleTouchCancel = function(e) { - this._hasMoved = false; - this._touchStartX = null; - this._touchStartY = null; - }; - - // With a click event, we need to check the target - // and if it's an internal target that doesn't want - // a click, cancel it - framework.EventController.prototype.handleClick = function(e) { - var target = e.target; - - if ( - ! target - || e.which > 1 - || e.metaKey - || e.ctrlKey - //|| isScrolling - || location.protocol !== target.protocol - || location.host !== target.host - // Not sure abotu this one - //|| !target.hash && /#/.test(target.href) - || target.hash && target.href.replace(target.hash, '') === location.href.replace(location.hash, '') - //|| target.getAttribute('data-ignore') == 'push' - ) { - // Allow it - console.log("EVENT: click", e); - return; - } - // We need to cancel this one - e.preventDefault(); - - // TODO: should we do this? - // e.stopPropagation(); - } - - framework.EventController.prototype.handlePopState = function(event) { - console.log("EVENT: popstate", event); - } // Map some convenient top-level functions for event handling - framework.on = framework.EventController.prototype.on; - framework.trigger = framework.EventController.prototype.trigger; - + framework.on = framework.EventController.on; + framework.trigger = framework.EventController.trigger; // Set up various listeners - window.addEventListener('touchstart', framework.EventController.prototype.handleTouchStart); - window.addEventListener('touchmove', framework.EventController.prototype.handleTouchMove); - window.addEventListener('touchcancel', framework.EventController.prototype.handleTouchCancel); - window.addEventListener('touchend', framework.EventController.prototype.handleTouchEnd); - window.addEventListener('click', framework.EventController.prototype.handleClick); - window.addEventListener('popstate', framework.EventController.prototype.handlePopState); + window.addEventListener('touchstart', framework.EventController.handleTouchStart); + window.addEventListener('touchmove', framework.EventController.handleTouchMove); + window.addEventListener('touchcancel', framework.EventController.handleTouchCancel); + window.addEventListener('touchend', framework.EventController.handleTouchEnd); + window.addEventListener('click', framework.EventController.handleClick); + window.addEventListener('popstate', framework.EventController.handlePopState); })(this, document, FM = this.FM || {}); diff --git a/js/framework/framework-gestures.js b/js/framework/framework-gestures.js new file mode 100644 index 0000000000..f65f0406ea --- /dev/null +++ b/js/framework/framework-gestures.js @@ -0,0 +1,267 @@ +/** + * Simple gesture controllers with some common gestures that emit + * gesture events. + * + * Much adapted from github.com/EightMedia/Hammer.js + */ +(function(window, document, framework) { + // Gesture support + framework.Gesture = {} + + // Simple touch gesture that triggers an event when an element is touched + framework.Gesture.Touch = { + handle: function(e) { + framework.EventController.trigger('touch', { + cancelable: true, + bubbles: true + }); + } + }; + + // Simple tap gesture + framework.Gesture.Tap = { + handle: function(e) { + switch(e.type) { + case 'touchstart': + // We are now touching + this._isTouching = true; + // Reset the movement indicator + this._hasMoved = false; + break; + case 'touchmove': + this._touchStartX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX; + this._touchStartY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY; + + var x = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX, + y = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY; + + // Check if the finger moved more than 10px, and then indicate we should cancel the tap + if (Math.abs(x - this._touchStartX) > 10 || Math.abs(y - this._touchStartY) > 10) { + this._hasMoved = true; + } + break; + case 'touchend': + if(this._hasMoved == false) { + framework.EventController.trigger('tap', { + cancelable: true, + bubbles: true + }); + } + break; + } + } + }; + + // A swipe-left gesture that emits the 'swipeleft' event when a left swipe + // is performed + framework.Gesture.SwipeLeft = { + handle: function(e) { + } + }; + + framework.GestureController = { + gestures: [ + framework.Gesture.Touch, + framework.Gesture.Tap + ], + detectGesture: function(e) { + var i; + + for(i = 0; i < this.gestures.length; i++) { + if(this.gestures[i].handle(e)) { + console.log('GESTURECONTROLLER: Gesture handled'); + return; + } + } + }, + + /** + * find if a node is in the given parent + * used for event delegation tricks + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @returns {boolean} has_parent + */ + hasParent: function(node, parent) { + while(node){ + if(node == parent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + + /** + * get the center of all the touches + * @param {Array} touches + * @returns {Object} center + */ + getCenter: function getCenter(touches) { + var valuesX = [], valuesY = []; + + for(var t= 0,len=touches.length; t= y) { + return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; + } + else { + return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; + } + }, + + + /** + * calculate the distance between two touches + * @param {Touch} touch1 + * @param {Touch} touch2 + * @returns {Number} distance + */ + getDistance: function getDistance(touch1, touch2) { + var x = touch2.pageX - touch1.pageX, + y = touch2.pageY - touch1.pageY; + return Math.sqrt((x*x) + (y*y)); + }, + + + /** + * calculate the scale factor between two touchLists (fingers) + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @param {Array} start + * @param {Array} end + * @returns {Number} scale + */ + getScale: function getScale(start, end) { + // need two fingers... + if(start.length >= 2 && end.length >= 2) { + return this.getDistance(end[0], end[1]) / + this.getDistance(start[0], start[1]); + } + return 1; + }, + + + /** + * calculate the rotation degrees between two touchLists (fingers) + * @param {Array} start + * @param {Array} end + * @returns {Number} rotation + */ + getRotation: function getRotation(start, end) { + // need two fingers + if(start.length >= 2 && end.length >= 2) { + return this.getAngle(end[1], end[0]) - + this.getAngle(start[1], start[0]); + } + return 0; + }, + + + /** + * boolean if the direction is vertical + * @param {String} direction + * @returns {Boolean} is_vertical + */ + isVertical: function isVertical(direction) { + return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN); + }, + + + /** + * stop browser default behavior with css props + * @param {HtmlElement} element + * @param {Object} css_props + */ + stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) { + var prop, + vendors = ['webkit','khtml','moz','Moz','ms','o','']; + + if(!css_props || !element.style) { + return; + } + + // with css properties for modern browsers + for(var i = 0; i < vendors.length; i++) { + for(var p in css_props) { + if(css_props.hasOwnProperty(p)) { + prop = p; + + // vender prefix at the property + if(vendors[i]) { + prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); + } + + // set the style + element.style[prop] = css_props[p]; + } + } + } + + // also the disable onselectstart + if(css_props.userSelect == 'none') { + element.onselectstart = function() { + return false; + }; + } + + // and disable ondragstart + if(css_props.userDrag == 'none') { + element.ondragstart = function() { + return false; + }; + } + } + + } + + +})(this, document, FM = this.FM || {});