diff --git a/example/events.html b/example/events.html
index b7a6291375..1dc83e3fbd 100644
--- a/example/events.html
+++ b/example/events.html
@@ -16,8 +16,8 @@
diff --git a/example/events.js b/example/events.js
index a0a0ca324f..dba0246e7c 100644
--- a/example/events.js
+++ b/example/events.js
@@ -5,35 +5,47 @@ var logEvent = function(data) {
console.log(data.event);
e.appendChild(l);
}
-window.FM.on('tap', function(e) {
+
+var tb = document.getElementById('tap-button');
+var sb = document.getElementById('swipe-button');
+
+window.FM.onGesture('tap', function(e) {
console.log('GOT TAP', e);
logEvent({
type: 'tap',
event: e
});
-});
-window.FM.on('touch', function(e) {
+}, tb);
+window.FM.onGesture('touch', function(e) {
console.log('GOT TOUCH', e);
logEvent({
type: 'touch',
event: e
});
-});
-window.FM.on('release', function(e) {
+}, tb);
+
+window.FM.onGesture('tap', function(e) {
+ console.log('GOT TAP', e);
+ logEvent({
+ type: 'tap',
+ event: e
+ });
+}, tb);
+window.FM.onGesture('release', function(e) {
console.log('GOT RELEASE', e);
logEvent({
type: 'release',
event: e
});
-});
-window.FM.on('swipe', function(e) {
+}, tb);
+window.FM.onGesture('swipe', function(e) {
console.log('GOT SWIPE', e);
logEvent({
type: 'swipe',
event: e
});
-});
-window.FM.on('swiperight', function(e) {
+}, sb);
+window.FM.onGesture('swiperight', function(e) {
console.log('GOT SWIPE RIGHT', e);
logEvent({
type: 'swiperight',
@@ -41,8 +53,8 @@ window.FM.on('swiperight', function(e) {
});
e.target.classList.add('swiperight');
-});
-window.FM.on('swipeleft', function(e) {
+}, sb);
+window.FM.onGesture('swipeleft', function(e) {
console.log('GOT SWIPE LEFT', e);
logEvent({
type: 'swipeleft',
@@ -50,18 +62,18 @@ window.FM.on('swipeleft', function(e) {
});
e.target.classList.add('swipeleft');
-});
-window.FM.on('swipeup', function(e) {
+}, sb);
+window.FM.onGesture('swipeup', function(e) {
console.log('GOT SWIPE UP', e);
logEvent({
type: 'swipeup',
event: e
});
-});
-window.FM.on('swipedown', function(e) {
+}, sb);
+window.FM.onGesture('swipedown', function(e) {
console.log('GOT SWIPE DOWN', e);
logEvent({
type: 'swipedown',
event: e
});
-});
+}, sb);
diff --git a/js/framework/framework-events.js b/js/framework/framework-events.js
index f74e8ef6b4..543eb64fa4 100644
--- a/js/framework/framework-events.js
+++ b/js/framework/framework-events.js
@@ -13,75 +13,36 @@
(function(window, document, framework) {
framework.EventController = {
- // A map of event types that we virtually detect and emit
- VIRTUAL_EVENT_TYPES: ['tap', 'swipeleft', 'swiperight'],
-
- /**
- * Trigger a new event.
- */
+ // Trigger a new event
trigger: function(eventType, data) {
// TODO: Do we need to use the old-school createEvent stuff?
var event = new CustomEvent(eventType, data);
+ // Make sure to trigger the event on the given target, or dispatch it from
+ // the window if we don't have an event target
data.target && data.target.dispatchEvent(event) || window.dispatchEvent(event);
},
- /**
- * Shorthand for binding a new event listener to the given
- * event type.
- */
+ // Bind an event
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
- return;
- }
- }
- */
-
- // Native listener
e.addEventListener(type, callback);
},
-
- /**
- * Process a touchstart event.
- */
- handleTouchStart: function(e) {
- console.log("EVENT: touchstart", e);
- framework.GestureController.startGesture(e);
+ off: function(type, callback, element) {
+ element.removeEventListener(type, callback);
},
- /**
- * Process a touchmove event.
- */
- handleTouchMove: function(e) {
- console.log("EVENT: touchmove", e);
- framework.GestureController.detectGesture(e);
-
+ // Register for a new gesture event on the given element
+ onGesture: function(type, callback, element) {
+ var gesture = new framework.Gesture(element);
+ gesture.on(type, callback);
+ return gesture;
},
- /**
- * 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;
+ // Unregister a previous gesture event
+ offGesture: function(gesture, type, callback) {
+ gesture.off(type, callback);
},
// With a click event, we need to check the target
@@ -122,13 +83,12 @@
// Map some convenient top-level functions for event handling
framework.on = framework.EventController.on;
+ framework.off = framework.EventController.off;
framework.trigger = framework.EventController.trigger;
+ framework.onGesture = framework.EventController.onGesture;
+ framework.offGesture = framework.EventController.offGesture;
// Set up various listeners
- 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);
diff --git a/js/framework/framework-gestures.js b/js/framework/framework-gestures.js
index 680b20e84a..a9e494687a 100644
--- a/js/framework/framework-gestures.js
+++ b/js/framework/framework-gestures.js
@@ -2,222 +2,557 @@
* Simple gesture controllers with some common gestures that emit
* gesture events.
*
- * Much adapted from github.com/EightMedia/Hammer.js - thanks!
+ * Ported from github.com/EightMedia/framework.Gestures.js - thanks!
*/
(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) {
- if(e.type == 'touchstart') {
- framework.GestureController.triggerGestureEvent('touch', e);
- }
- }
+
+ /**
+ * framework.Gestures
+ * use this to create instances
+ * @param {HTMLElement} element
+ * @param {Object} options
+ * @returns {framework.Gestures.Instance}
+ * @constructor
+ */
+ framework.Gesture = function(element, options) {
+ return new framework.Gestures.Instance(element, options || {});
};
- // Simple tap gesture
- framework.Gesture.Tap = {
- handle: function(e) {
- switch(e.type) {
- case 'touchstart':
- this._touchStartX = e.touches[0].clientX;
- this._touchStartY = e.touches[0].clientY;
+ framework.Gestures = {};
- // We are now touching
- this._isTouching = true;
- // Reset the movement indicator
- this._hasMoved = false;
- break;
- case 'touchmove':
- var x = e.touches[0].clientX;
- y = e.touches[0].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) {
- console.log('HAS MOVED');
- this._hasMoved = true;
- }
- break;
- case 'touchend':
- if(this._hasMoved == false) {
- framework.GestureController.triggerGestureEvent('tap', e);
- }
- break;
- }
+ // default settings
+ framework.Gestures.defaults = {
+ // add styles and attributes to the element to prevent the browser from doing
+ // its native behavior. this doesnt prevent the scrolling, but cancels
+ // the contextmenu, tap highlighting etc
+ // set to false to disable this
+ stop_browser_behavior: {
+ // this also triggers onselectstart=false for IE
+ userSelect: 'none',
+ // this makes the element blocking in IE10 >, you could experiment with the value
+ // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
+ touchAction: 'none',
+ touchCallout: 'none',
+ contentZooming: 'none',
+ userDrag: 'none',
+ tapHighlightColor: 'rgba(0,0,0,0)'
}
+
+ // more settings are defined per gesture at gestures.js
};
- // The gesture is over, trigger a release event
- framework.Gesture.Release = {
- handle: function(e) {
- if(e.type === 'touchend') {
- framework.GestureController.triggerGestureEvent('release', e);
+ // detect touchevents
+ framework.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
+ framework.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
+
+ // dont use mouseevents on mobile devices
+ framework.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
+ framework.Gestures.NO_MOUSEEVENTS = framework.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(framework.Gestures.MOBILE_REGEX);
+
+ // eventtypes per touchevent (start, move, end)
+ // are filled by framework.Gestures.event.determineEventTypes on setup
+ framework.Gestures.EVENT_TYPES = {};
+
+ // direction defines
+ framework.Gestures.DIRECTION_DOWN = 'down';
+ framework.Gestures.DIRECTION_LEFT = 'left';
+ framework.Gestures.DIRECTION_UP = 'up';
+ framework.Gestures.DIRECTION_RIGHT = 'right';
+
+ // pointer type
+ framework.Gestures.POINTER_MOUSE = 'mouse';
+ framework.Gestures.POINTER_TOUCH = 'touch';
+ framework.Gestures.POINTER_PEN = 'pen';
+
+ // touch event defines
+ framework.Gestures.EVENT_START = 'start';
+ framework.Gestures.EVENT_MOVE = 'move';
+ framework.Gestures.EVENT_END = 'end';
+
+ // hammer document where the base events are added at
+ framework.Gestures.DOCUMENT = window.document;
+
+ // plugins namespace
+ framework.Gestures.plugins = {};
+
+ // if the window events are set...
+ framework.Gestures.READY = false;
+
+ /**
+ * setup events to detect gestures on the document
+ */
+ function setup() {
+ if(framework.Gestures.READY) {
+ return;
+ }
+
+ // find what eventtypes we add listeners to
+ framework.Gestures.event.determineEventTypes();
+
+ // Register all gestures inside framework.Gestures.gestures
+ for(var name in framework.Gestures.gestures) {
+ if(framework.Gestures.gestures.hasOwnProperty(name)) {
+ framework.Gestures.detection.register(framework.Gestures.gestures[name]);
}
}
- };
- // A swipe gesture that emits the 'swipe' event when a left swipe happens
- framework.Gesture.Swipe = {
- swipe_velocity: 0.7,
- handle: function(e) {
- if(e.type == 'touchend') {
+ // Add touch events on the document
+ framework.Gestures.event.onTouch(framework.Gestures.DOCUMENT, framework.Gestures.EVENT_MOVE, framework.Gestures.detection.detect);
+ framework.Gestures.event.onTouch(framework.Gestures.DOCUMENT, framework.Gestures.EVENT_END, framework.Gestures.detection.detect);
- if(e.velocityX > this.swipe_velocity ||
- e.velocityY > this.swipe_velocity) {
+ // framework.Gestures is ready...!
+ framework.Gestures.READY = true;
+ }
- // trigger swipe events, both a general swipe,
- // and a directional swipe
- framework.GestureController.triggerGestureEvent('swipe', e);
- framework.GestureController.triggerGestureEvent('swipe' + e.direction, e);
- }
- }
+ /**
+ * create new hammer instance
+ * all methods should return the instance itself, so it is chainable.
+ * @param {HTMLElement} element
+ * @param {Object} [options={}]
+ * @returns {framework.Gestures.Instance}
+ * @constructor
+ */
+ framework.Gestures.Instance = function(element, options) {
+ var self = this;
+
+ // setup framework.GesturesJS window events and register all gestures
+ // this also sets up the default options
+ setup();
+
+ this.element = element;
+
+ // start/stop detection option
+ this.enabled = true;
+
+ // merge options
+ this.options = framework.Gestures.utils.extend(
+ framework.Gestures.utils.extend({}, framework.Gestures.defaults),
+ options || {});
+
+ // add some css to the element to prevent the browser from doing its native behavoir
+ if(this.options.stop_browser_behavior) {
+ framework.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
}
+
+ // start detection on touchstart
+ framework.Gestures.event.onTouch(element, framework.Gestures.EVENT_START, function(ev) {
+ if(self.enabled) {
+ framework.Gestures.detection.startDetect(self, ev);
+ }
+ });
+
+ // return instance
+ return this;
};
- framework.GestureController = {
- gestures: [
- framework.Gesture.Touch,
- framework.Gesture.Tap,
- framework.Gesture.Swipe,
- framework.Gesture.Release,
- ],
-
- triggerGestureEvent: function(type, e) {
- framework.EventController.trigger(type, framework.Utils.extend({}, e));
+ framework.Gestures.Instance.prototype = {
+ /**
+ * bind events to the instance
+ * @param {String} gesture
+ * @param {Function} handler
+ * @returns {framework.Gestures.Instance}
+ */
+ on: function onEvent(gesture, handler){
+ var gestures = gesture.split(' ');
+ for(var t=0; t 0 && eventType == framework.Gestures.EVENT_END) {
+ eventType = framework.Gestures.EVENT_MOVE;
+ }
+ // no touches, force the end event
+ else if(!count_touches) {
+ eventType = framework.Gestures.EVENT_END;
+ }
+
+ // store the last move event
+ if(count_touches || last_move_event === null) {
+ last_move_event = ev;
+ }
+
+ // trigger the handler
+ handler.call(framework.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
+
+ // remove pointerevent from list
+ if(framework.Gestures.HAS_POINTEREVENTS && eventType == framework.Gestures.EVENT_END) {
+ count_touches = framework.Gestures.PointerEvent.updatePointer(eventType, ev);
+ }
+ }
+
+ //debug(sourceEventType +" "+ eventType);
+
+ // on the end we reset everything
+ if(!count_touches) {
+ last_move_event = null;
+ enable_detect = false;
+ touch_triggered = false;
+ framework.Gestures.PointerEvent.reset();
+ }
+ });
+ },
+
+
+ /**
+ * we have different events for each device/browser
+ * determine what we need and set them in the framework.Gestures.EVENT_TYPES constant
+ */
+ determineEventTypes: function determineEventTypes() {
+ // determine the eventtype we want to set
+ var types;
+
+ // pointerEvents magic
+ if(framework.Gestures.HAS_POINTEREVENTS) {
+ types = framework.Gestures.PointerEvent.getEvents();
+ }
+ // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
+ else if(framework.Gestures.NO_MOUSEEVENTS) {
+ types = [
+ 'touchstart',
+ 'touchmove',
+ 'touchend touchcancel'];
+ }
+ // for non pointer events browsers and mixed browsers,
+ // like chrome on windows8 touch laptop
+ else {
+ types = [
+ 'touchstart mousedown',
+ 'touchmove mousemove',
+ 'touchend touchcancel mouseup'];
}
- if(this.currentGesture) {
- // Store this event so we can access it again later
- this.currentGesture.lastEvent = eventData;
+ framework.Gestures.EVENT_TYPES[framework.Gestures.EVENT_START] = types[0];
+ framework.Gestures.EVENT_TYPES[framework.Gestures.EVENT_MOVE] = types[1];
+ framework.Gestures.EVENT_TYPES[framework.Gestures.EVENT_END] = types[2];
+ },
+
+
+ /**
+ * create touchlist depending on the event
+ * @param {Object} ev
+ * @param {String} eventType used by the fakemultitouch plugin
+ */
+ getTouchList: function getTouchList(ev/*, eventType*/) {
+ // get the fake pointerEvent touchlist
+ if(framework.Gestures.HAS_POINTEREVENTS) {
+ return framework.Gestures.PointerEvent.getTouchList();
+ }
+ // get the touchlist
+ else if(ev.touches) {
+ return ev.touches;
+ }
+ // make fake touchlist from mouse position
+ else {
+ ev.indentifier = 1;
+ return [ev];
+ }
+ },
+
+
+ /**
+ * collect event data for framework.Gestures js
+ * @param {HTMLElement} element
+ * @param {String} eventType like framework.Gestures.EVENT_MOVE
+ * @param {Object} eventData
+ */
+ collectEventData: function collectEventData(element, eventType, touches, ev) {
+
+ // find out pointerType
+ var pointerType = framework.Gestures.POINTER_TOUCH;
+ if(ev.type.match(/mouse/) || framework.Gestures.PointerEvent.matchType(framework.Gestures.POINTER_MOUSE, ev)) {
+ pointerType = framework.Gestures.POINTER_MOUSE;
}
- // It's over!
- if(e.type === 'touchend' || e.type === 'touchcancel') {
- this.endGesture(eventData);
+ return {
+ center : framework.Gestures.utils.getCenter(touches),
+ timeStamp : new Date().getTime(),
+ target : ev.target,
+ touches : touches,
+ eventType : eventType,
+ pointerType : pointerType,
+ srcEvent : ev,
+
+ /**
+ * prevent the browser default actions
+ * mostly used to disable scrolling of the browser
+ */
+ preventDefault: function() {
+ if(this.srcEvent.preventManipulation) {
+ this.srcEvent.preventManipulation();
+ }
+
+ if(this.srcEvent.preventDefault) {
+ this.srcEvent.preventDefault();
+ }
+ },
+
+ /**
+ * stop bubbling the event up to its parents
+ */
+ stopPropagation: function() {
+ this.srcEvent.stopPropagation();
+ },
+
+ /**
+ * immediately stop gesture detection
+ * might be useful after a swipe was detected
+ * @return {*}
+ */
+ stopDetect: function() {
+ return framework.Gestures.detection.stopDetect();
+ }
+ };
+ }
+ };
+
+ framework.Gestures.PointerEvent = {
+ /**
+ * holds all pointers
+ * @type {Object}
+ */
+ pointers: {},
+
+ /**
+ * get a list of pointers
+ * @returns {Array} touchlist
+ */
+ getTouchList: function() {
+ var self = this;
+ var touchlist = [];
+
+ // we can use forEach since pointerEvents only is in IE10
+ Object.keys(self.pointers).sort().forEach(function(id) {
+ touchlist.push(self.pointers[id]);
+ });
+ return touchlist;
+ },
+
+ /**
+ * update the position of a pointer
+ * @param {String} type framework.Gestures.EVENT_END
+ * @param {Object} pointerEvent
+ */
+ updatePointer: function(type, pointerEvent) {
+ if(type == framework.Gestures.EVENT_END) {
+ this.pointers = {};
}
+ else {
+ pointerEvent.identifier = pointerEvent.pointerId;
+ this.pointers[pointerEvent.pointerId] = pointerEvent;
+ }
+
+ return Object.keys(this.pointers).length;
},
- endGesture: function(e) {
- this.currentGesture = null;
- this._lastMoveEvent = null;
+
+ /**
+ * check if ev matches pointertype
+ * @param {String} pointerType framework.Gestures.POINTER_MOUSE
+ * @param {PointerEvent} ev
+ */
+ matchType: function(pointerType, ev) {
+ if(!ev.pointerType) {
+ return false;
+ }
+
+ var types = {};
+ types[framework.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == framework.Gestures.POINTER_MOUSE);
+ types[framework.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == framework.Gestures.POINTER_TOUCH);
+ types[framework.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == framework.Gestures.POINTER_PEN);
+ return types[pointerType];
},
+
+ /**
+ * get events
+ */
+ getEvents: function() {
+ return [
+ 'pointerdown MSPointerDown',
+ 'pointermove MSPointerMove',
+ 'pointerup pointercancel MSPointerUp MSPointerCancel'
+ ];
+ },
+
+ /**
+ * reset the list
+ */
+ reset: function() {
+ this.pointers = {};
+ }
+ };
+
+
+ framework.Gestures.utils = {
+ /**
+ * extend method,
+ * also used for cloning when dest is an empty object
+ * @param {Object} dest
+ * @param {Object} src
+ * @parm {Boolean} merge do a merge
+ * @returns {Object} dest
+ */
+ extend: function extend(dest, src, merge) {
+ for (var key in src) {
+ if(dest[key] !== undefined && merge) {
+ continue;
+ }
+ dest[key] = src[key];
+ }
+ return dest;
+ },
+
+
/**
* find if a node is in the given parent
* used for event delegation tricks
@@ -226,13 +561,13 @@
* @returns {boolean} has_parent
*/
hasParent: function(node, parent) {
- while(node){
- if(node == parent) {
- return true;
- }
- node = node.parentNode;
+ while(node){
+ if(node == parent) {
+ return true;
}
- return false;
+ node = node.parentNode;
+ }
+ return false;
},
@@ -242,17 +577,17 @@
* @returns {Object} center
*/
getCenter: function getCenter(touches) {
- var valuesX = [], valuesY = [];
+ var valuesX = [], valuesY = [];
- for(var t= 0,len=touches.length; t= y) {
- return touch1.pageX - touch2.pageX > 0 ? 'left' : 'right';
- }
- else {
- return touch1.pageY - touch2.pageY > 0 ? 'up': 'down';
- }
+ if(x >= y) {
+ return touch1.pageX - touch2.pageX > 0 ? framework.Gestures.DIRECTION_LEFT : framework.Gestures.DIRECTION_RIGHT;
+ }
+ else {
+ return touch1.pageY - touch2.pageY > 0 ? framework.Gestures.DIRECTION_UP : framework.Gestures.DIRECTION_DOWN;
+ }
},
@@ -310,9 +645,9 @@
* @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));
+ var x = touch2.pageX - touch1.pageX,
+ y = touch2.pageY - touch1.pageY;
+ return Math.sqrt((x*x) + (y*y));
},
@@ -324,12 +659,12 @@
* @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;
+ // 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;
},
@@ -340,12 +675,12 @@
* @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;
+ // 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;
},
@@ -355,7 +690,7 @@
* @returns {Boolean} is_vertical
*/
isVertical: function isVertical(direction) {
- return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN);
+ return (direction == framework.Gestures.DIRECTION_UP || direction == framework.Gestures.DIRECTION_DOWN);
},
@@ -365,46 +700,721 @@
* @param {Object} css_props
*/
stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) {
- var prop,
- vendors = ['webkit','khtml','moz','Moz','ms','o',''];
+ var prop,
+ vendors = ['webkit','khtml','moz','Moz','ms','o',''];
- if(!css_props || !element.style) {
- return;
- }
+ 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;
+ // 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];
- }
+ // vender prefix at the property
+ if(vendors[i]) {
+ prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
}
- }
- // 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;
- };
+ // set the style
+ element.style[prop] = css_props[p];
+ }
}
+ }
+
+ // also the disable onselectstart
+ if(css_props.userSelect == 'none') {
+ element.onselectstart = function() {
+ return false;
+ };
+ }
}
-
- }
+ };
+ framework.Gestures.detection = {
+ // contains all registred framework.Gestures.gestures in the correct order
+ gestures: [],
+
+ // data of the current framework.Gestures.gesture detection session
+ current: null,
+
+ // the previous framework.Gestures.gesture session data
+ // is a full clone of the previous gesture.current object
+ previous: null,
+
+ // when this becomes true, no gestures are fired
+ stopped: false,
+
+
+ /**
+ * start framework.Gestures.gesture detection
+ * @param {framework.Gestures.Instance} inst
+ * @param {Object} eventData
+ */
+ startDetect: function startDetect(inst, eventData) {
+ // already busy with a framework.Gestures.gesture detection on an element
+ if(this.current) {
+ return;
+ }
+
+ this.stopped = false;
+
+ this.current = {
+ inst : inst, // reference to framework.GesturesInstance we're working for
+ startEvent : framework.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
+ lastEvent : false, // last eventData
+ name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
+ };
+
+ this.detect(eventData);
+ },
+
+
+ /**
+ * framework.Gestures.gesture detection
+ * @param {Object} eventData
+ */
+ detect: function detect(eventData) {
+ if(!this.current || this.stopped) {
+ return;
+ }
+
+ // extend event data with calculations about scale, distance etc
+ eventData = this.extendEventData(eventData);
+
+ // instance options
+ var inst_options = this.current.inst.options;
+
+ // call framework.Gestures.gesture handlers
+ for(var g=0,len=this.gestures.length; g b.index) {
+ return 1;
+ }
+ return 0;
+ });
+
+ return this.gestures;
+ }
+ };
+
+
+ framework.Gestures.gestures = framework.Gestures.gestures || {};
+
+ /**
+ * Custom gestures
+ * ==============================
+ *
+ * Gesture object
+ * --------------------
+ * The object structure of a gesture:
+ *
+ * { name: 'mygesture',
+ * index: 1337,
+ * defaults: {
+ * mygesture_option: true
+ * }
+ * handler: function(type, ev, inst) {
+ * // trigger gesture event
+ * inst.trigger(this.name, ev);
+ * }
+ * }
+
+ * @param {String} name
+ * this should be the name of the gesture, lowercase
+ * it is also being used to disable/enable the gesture per instance config.
+ *
+ * @param {Number} [index=1000]
+ * the index of the gesture, where it is going to be in the stack of gestures detection
+ * like when you build an gesture that depends on the drag gesture, it is a good
+ * idea to place it after the index of the drag gesture.
+ *
+ * @param {Object} [defaults={}]
+ * the default settings of the gesture. these are added to the instance settings,
+ * and can be overruled per instance. you can also add the name of the gesture,
+ * but this is also added by default (and set to true).
+ *
+ * @param {Function} handler
+ * this handles the gesture detection of your custom gesture and receives the
+ * following arguments:
+ *
+ * @param {Object} eventData
+ * event data containing the following properties:
+ * timeStamp {Number} time the event occurred
+ * target {HTMLElement} target element
+ * touches {Array} touches (fingers, pointers, mouse) on the screen
+ * pointerType {String} kind of pointer that was used. matches framework.Gestures.POINTER_MOUSE|TOUCH
+ * center {Object} center position of the touches. contains pageX and pageY
+ * deltaTime {Number} the total time of the touches in the screen
+ * deltaX {Number} the delta on x axis we haved moved
+ * deltaY {Number} the delta on y axis we haved moved
+ * velocityX {Number} the velocity on the x
+ * velocityY {Number} the velocity on y
+ * angle {Number} the angle we are moving
+ * direction {String} the direction we are moving. matches framework.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
+ * distance {Number} the distance we haved moved
+ * scale {Number} scaling of the touches, needs 2 touches
+ * rotation {Number} rotation of the touches, needs 2 touches *
+ * eventType {String} matches framework.Gestures.EVENT_START|MOVE|END
+ * srcEvent {Object} the source event, like TouchStart or MouseDown *
+ * startEvent {Object} contains the same properties as above,
+ * but from the first touch. this is used to calculate
+ * distances, deltaTime, scaling etc
+ *
+ * @param {framework.Gestures.Instance} inst
+ * the instance we are doing the detection for. you can get the options from
+ * the inst.options object and trigger the gesture event by calling inst.trigger
+ *
+ *
+ * Handle gestures
+ * --------------------
+ * inside the handler you can get/set framework.Gestures.detection.current. This is the current
+ * detection session. It has the following properties
+ * @param {String} name
+ * contains the name of the gesture we have detected. it has not a real function,
+ * only to check in other gestures if something is detected.
+ * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
+ * check if the current gesture is 'drag' by accessing framework.Gestures.detection.current.name
+ *
+ * @readonly
+ * @param {framework.Gestures.Instance} inst
+ * the instance we do the detection for
+ *
+ * @readonly
+ * @param {Object} startEvent
+ * contains the properties of the first gesture detection in this session.
+ * Used for calculations about timing, distance, etc.
+ *
+ * @readonly
+ * @param {Object} lastEvent
+ * contains all the properties of the last gesture detect in this session.
+ *
+ * after the gesture detection session has been completed (user has released the screen)
+ * the framework.Gestures.detection.current object is copied into framework.Gestures.detection.previous,
+ * this is usefull for gestures like doubletap, where you need to know if the
+ * previous gesture was a tap
+ *
+ * options that have been set by the instance can be received by calling inst.options
+ *
+ * You can trigger a gesture event by calling inst.trigger("mygesture", event).
+ * The first param is the name of your gesture, the second the event argument
+ *
+ *
+ * Register gestures
+ * --------------------
+ * When an gesture is added to the framework.Gestures.gestures object, it is auto registered
+ * at the setup of the first framework.Gestures instance. You can also call framework.Gestures.detection.register
+ * manually and pass your gesture object as a param
+ *
+ */
+
+ /**
+ * Hold
+ * Touch stays at the same place for x time
+ * @events hold
+ */
+ framework.Gestures.gestures.Hold = {
+ name: 'hold',
+ index: 10,
+ defaults: {
+ hold_timeout : 500,
+ hold_threshold : 1
+ },
+ timer: null,
+ handler: function holdGesture(ev, inst) {
+ switch(ev.eventType) {
+ case framework.Gestures.EVENT_START:
+ // clear any running timers
+ clearTimeout(this.timer);
+
+ // set the gesture so we can check in the timeout if it still is
+ framework.Gestures.detection.current.name = this.name;
+
+ // set timer and if after the timeout it still is hold,
+ // we trigger the hold event
+ this.timer = setTimeout(function() {
+ if(framework.Gestures.detection.current.name == 'hold') {
+ inst.trigger('hold', ev);
+ }
+ }, inst.options.hold_timeout);
+ break;
+
+ // when you move or end we clear the timer
+ case framework.Gestures.EVENT_MOVE:
+ if(ev.distance > inst.options.hold_threshold) {
+ clearTimeout(this.timer);
+ }
+ break;
+
+ case framework.Gestures.EVENT_END:
+ clearTimeout(this.timer);
+ break;
+ }
+ }
+ };
+
+
+ /**
+ * Tap/DoubleTap
+ * Quick touch at a place or double at the same place
+ * @events tap, doubletap
+ */
+ framework.Gestures.gestures.Tap = {
+ name: 'tap',
+ index: 100,
+ defaults: {
+ tap_max_touchtime : 250,
+ tap_max_distance : 10,
+ tap_always : true,
+ doubletap_distance : 20,
+ doubletap_interval : 300
+ },
+ handler: function tapGesture(ev, inst) {
+ if(ev.eventType == framework.Gestures.EVENT_END) {
+ // previous gesture, for the double tap since these are two different gesture detections
+ var prev = framework.Gestures.detection.previous,
+ did_doubletap = false;
+
+ // when the touchtime is higher then the max touch time
+ // or when the moving distance is too much
+ if(ev.deltaTime > inst.options.tap_max_touchtime ||
+ ev.distance > inst.options.tap_max_distance) {
+ return;
+ }
+
+ // check if double tap
+ if(prev && prev.name == 'tap' &&
+ (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
+ ev.distance < inst.options.doubletap_distance) {
+ inst.trigger('doubletap', ev);
+ did_doubletap = true;
+ }
+
+ // do a single tap
+ if(!did_doubletap || inst.options.tap_always) {
+ framework.Gestures.detection.current.name = 'tap';
+ inst.trigger(framework.Gestures.detection.current.name, ev);
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Swipe
+ * triggers swipe events when the end velocity is above the threshold
+ * @events swipe, swipeleft, swiperight, swipeup, swipedown
+ */
+ framework.Gestures.gestures.Swipe = {
+ name: 'swipe',
+ index: 40,
+ defaults: {
+ // set 0 for unlimited, but this can conflict with transform
+ swipe_max_touches : 1,
+ swipe_velocity : 0.7
+ },
+ handler: function swipeGesture(ev, inst) {
+ if(ev.eventType == framework.Gestures.EVENT_END) {
+ // max touches
+ if(inst.options.swipe_max_touches > 0 &&
+ ev.touches.length > inst.options.swipe_max_touches) {
+ return;
+ }
+
+ // when the distance we moved is too small we skip this gesture
+ // or we can be already in dragging
+ if(ev.velocityX > inst.options.swipe_velocity ||
+ ev.velocityY > inst.options.swipe_velocity) {
+ // trigger swipe events
+ inst.trigger(this.name, ev);
+ inst.trigger(this.name + ev.direction, ev);
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Drag
+ * Move with x fingers (default 1) around on the page. Blocking the scrolling when
+ * moving left and right is a good practice. When all the drag events are blocking
+ * you disable scrolling on that area.
+ * @events drag, drapleft, dragright, dragup, dragdown
+ */
+ framework.Gestures.gestures.Drag = {
+ name: 'drag',
+ index: 50,
+ defaults: {
+ drag_min_distance : 10,
+ // Set correct_for_drag_min_distance to true to make the starting point of the drag
+ // be calculated from where the drag was triggered, not from where the touch started.
+ // Useful to avoid a jerk-starting drag, which can make fine-adjustments
+ // through dragging difficult, and be visually unappealing.
+ correct_for_drag_min_distance : true,
+ // set 0 for unlimited, but this can conflict with transform
+ drag_max_touches : 1,
+ // prevent default browser behavior when dragging occurs
+ // be careful with it, it makes the element a blocking element
+ // when you are using the drag gesture, it is a good practice to set this true
+ drag_block_horizontal : false,
+ drag_block_vertical : false,
+ // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
+ // It disallows vertical directions if the initial direction was horizontal, and vice versa.
+ drag_lock_to_axis : false,
+ // drag lock only kicks in when distance > drag_lock_min_distance
+ // This way, locking occurs only when the distance has become large enough to reliably determine the direction
+ drag_lock_min_distance : 25
+ },
+ triggered: false,
+ handler: function dragGesture(ev, inst) {
+ // current gesture isnt drag, but dragged is true
+ // this means an other gesture is busy. now call dragend
+ if(framework.Gestures.detection.current.name != this.name && this.triggered) {
+ inst.trigger(this.name +'end', ev);
+ this.triggered = false;
+ return;
+ }
+
+ // max touches
+ if(inst.options.drag_max_touches > 0 &&
+ ev.touches.length > inst.options.drag_max_touches) {
+ return;
+ }
+
+ switch(ev.eventType) {
+ case framework.Gestures.EVENT_START:
+ this.triggered = false;
+ break;
+
+ case framework.Gestures.EVENT_MOVE:
+ // when the distance we moved is too small we skip this gesture
+ // or we can be already in dragging
+ if(ev.distance < inst.options.drag_min_distance &&
+ framework.Gestures.detection.current.name != this.name) {
+ return;
+ }
+
+ // we are dragging!
+ if(framework.Gestures.detection.current.name != this.name) {
+ framework.Gestures.detection.current.name = this.name;
+ if (inst.options.correct_for_drag_min_distance) {
+ // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
+ // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
+ // It might be useful to save the original start point somewhere
+ var factor = Math.abs(inst.options.drag_min_distance/ev.distance);
+ framework.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
+ framework.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
+
+ // recalculate event data using new start point
+ ev = framework.Gestures.detection.extendEventData(ev);
+ }
+ }
+
+ // lock drag to axis?
+ if(framework.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) {
+ ev.drag_locked_to_axis = true;
+ }
+ var last_direction = framework.Gestures.detection.current.lastEvent.direction;
+ if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
+ // keep direction on the axis that the drag gesture started on
+ if(framework.Gestures.utils.isVertical(last_direction)) {
+ ev.direction = (ev.deltaY < 0) ? framework.Gestures.DIRECTION_UP : framework.Gestures.DIRECTION_DOWN;
+ }
+ else {
+ ev.direction = (ev.deltaX < 0) ? framework.Gestures.DIRECTION_LEFT : framework.Gestures.DIRECTION_RIGHT;
+ }
+ }
+
+ // first time, trigger dragstart event
+ if(!this.triggered) {
+ inst.trigger(this.name +'start', ev);
+ this.triggered = true;
+ }
+
+ // trigger normal event
+ inst.trigger(this.name, ev);
+
+ // direction event, like dragdown
+ inst.trigger(this.name + ev.direction, ev);
+
+ // block the browser events
+ if( (inst.options.drag_block_vertical && framework.Gestures.utils.isVertical(ev.direction)) ||
+ (inst.options.drag_block_horizontal && !framework.Gestures.utils.isVertical(ev.direction))) {
+ ev.preventDefault();
+ }
+ break;
+
+ case framework.Gestures.EVENT_END:
+ // trigger dragend
+ if(this.triggered) {
+ inst.trigger(this.name +'end', ev);
+ }
+
+ this.triggered = false;
+ break;
+ }
+ }
+ };
+
+
+ /**
+ * Transform
+ * User want to scale or rotate with 2 fingers
+ * @events transform, pinch, pinchin, pinchout, rotate
+ */
+ framework.Gestures.gestures.Transform = {
+ name: 'transform',
+ index: 45,
+ defaults: {
+ // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
+ transform_min_scale : 0.01,
+ // rotation in degrees
+ transform_min_rotation : 1,
+ // prevent default browser behavior when two touches are on the screen
+ // but it makes the element a blocking element
+ // when you are using the transform gesture, it is a good practice to set this true
+ transform_always_block : false
+ },
+ triggered: false,
+ handler: function transformGesture(ev, inst) {
+ // current gesture isnt drag, but dragged is true
+ // this means an other gesture is busy. now call dragend
+ if(framework.Gestures.detection.current.name != this.name && this.triggered) {
+ inst.trigger(this.name +'end', ev);
+ this.triggered = false;
+ return;
+ }
+
+ // atleast multitouch
+ if(ev.touches.length < 2) {
+ return;
+ }
+
+ // prevent default when two fingers are on the screen
+ if(inst.options.transform_always_block) {
+ ev.preventDefault();
+ }
+
+ switch(ev.eventType) {
+ case framework.Gestures.EVENT_START:
+ this.triggered = false;
+ break;
+
+ case framework.Gestures.EVENT_MOVE:
+ var scale_threshold = Math.abs(1-ev.scale);
+ var rotation_threshold = Math.abs(ev.rotation);
+
+ // when the distance we moved is too small we skip this gesture
+ // or we can be already in dragging
+ if(scale_threshold < inst.options.transform_min_scale &&
+ rotation_threshold < inst.options.transform_min_rotation) {
+ return;
+ }
+
+ // we are transforming!
+ framework.Gestures.detection.current.name = this.name;
+
+ // first time, trigger dragstart event
+ if(!this.triggered) {
+ inst.trigger(this.name +'start', ev);
+ this.triggered = true;
+ }
+
+ inst.trigger(this.name, ev); // basic transform event
+
+ // trigger rotate event
+ if(rotation_threshold > inst.options.transform_min_rotation) {
+ inst.trigger('rotate', ev);
+ }
+
+ // trigger pinch event
+ if(scale_threshold > inst.options.transform_min_scale) {
+ inst.trigger('pinch', ev);
+ inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev);
+ }
+ break;
+
+ case framework.Gestures.EVENT_END:
+ // trigger dragend
+ if(this.triggered) {
+ inst.trigger(this.name +'end', ev);
+ }
+
+ this.triggered = false;
+ break;
+ }
+ }
+ };
+
+
+ /**
+ * Touch
+ * Called as first, tells the user has touched the screen
+ * @events touch
+ */
+ framework.Gestures.gestures.Touch = {
+ name: 'touch',
+ index: -Infinity,
+ defaults: {
+ // call preventDefault at touchstart, and makes the element blocking by
+ // disabling the scrolling of the page, but it improves gestures like
+ // transforming and dragging.
+ // be careful with using this, it can be very annoying for users to be stuck
+ // on the page
+ prevent_default: false,
+
+ // disable mouse events, so only touch (or pen!) input triggers events
+ prevent_mouseevents: false
+ },
+ handler: function touchGesture(ev, inst) {
+ if(inst.options.prevent_mouseevents && ev.pointerType == framework.Gestures.POINTER_MOUSE) {
+ ev.stopDetect();
+ return;
+ }
+
+ if(inst.options.prevent_default) {
+ ev.preventDefault();
+ }
+
+ if(ev.eventType == framework.Gestures.EVENT_START) {
+ inst.trigger(this.name, ev);
+ }
+ }
+ };
+
+
+ /**
+ * Release
+ * Called as last, tells the user has released the screen
+ * @events release
+ */
+ framework.Gestures.gestures.Release = {
+ name: 'release',
+ index: Infinity,
+ handler: function releaseGesture(ev, inst) {
+ if(ev.eventType == framework.Gestures.EVENT_END) {
+ inst.trigger(this.name, ev);
+ }
+ }
+ };
})(this, document, FM = this.FM || {});