forerunnerdb
Version:
A NoSQL document store database for browsers and Node.js.
1,578 lines (1,362 loc) • 2.27 MB
JavaScript
/*!
* ionic.bundle.js is a concatenation of:
* ionic.js, angular.js, angular-animate.js,
* angular-sanitize.js, angular-ui-router.js,
* and ionic-angular.js
*/
/*!
* Copyright 2015 Drifty Co.
* http://drifty.com/
*
* Ionic, v1.2.4
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
* By @maxlynch, @benjsperry, @adamdbradley <3
*
* Licensed under the MIT license. Please see LICENSE for more information.
*
*/
(function() {
// Create global ionic obj and its namespaces
// build processes may have already created an ionic obj
window.ionic = window.ionic || {};
window.ionic.views = {};
window.ionic.version = '1.2.4';
(function (ionic) {
ionic.DelegateService = function(methodNames) {
if (methodNames.indexOf('$getByHandle') > -1) {
throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
}
function trueFn() { return true; }
return ['$log', function($log) {
/*
* Creates a new object that will have all the methodNames given,
* and call them on the given the controller instance matching given
* handle.
* The reason we don't just let $getByHandle return the controller instance
* itself is that the controller instance might not exist yet.
*
* We want people to be able to do
* `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
* instantiation, but on controller instantiation a child directive
* may not have been compiled yet!
*
* So this is our way of solving this problem: we create an object
* that will only try to fetch the controller with given handle
* once the methods are actually called.
*/
function DelegateInstance(instances, handle) {
this._instances = instances;
this.handle = handle;
}
methodNames.forEach(function(methodName) {
DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
});
/**
* The delegate service (eg $ionicNavBarDelegate) is just an instance
* with a non-defined handle, a couple extra methods for registering
* and narrowing down to a specific handle.
*/
function DelegateService() {
this._instances = [];
}
DelegateService.prototype = DelegateInstance.prototype;
DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
var instances = this._instances;
instance.$$delegateHandle = handle;
instance.$$filterFn = filterFn || trueFn;
instances.push(instance);
return function deregister() {
var index = instances.indexOf(instance);
if (index !== -1) {
instances.splice(index, 1);
}
};
};
DelegateService.prototype.$getByHandle = function(handle) {
return new DelegateInstance(this._instances, handle);
};
return new DelegateService();
function instanceMethodCaller(methodName) {
return function caller() {
var handle = this.handle;
var args = arguments;
var foundInstancesCount = 0;
var returnValue;
this._instances.forEach(function(instance) {
if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
foundInstancesCount++;
var ret = instance[methodName].apply(instance, args);
//Only return the value from the first call
if (foundInstancesCount === 1) {
returnValue = ret;
}
}
});
if (!foundInstancesCount && handle) {
return $log.warn(
'Delegate for handle "' + handle + '" could not find a ' +
'corresponding element with delegate-handle="' + handle + '"! ' +
methodName + '() was not called!\n' +
'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
'your element with delegate-handle="' + handle + '" is a child of your ' +
'controller, then your element may not be compiled yet. Put a $timeout ' +
'around your call to ' + methodName + '() and try again.'
);
}
return returnValue;
};
}
}];
};
})(window.ionic);
(function(window, document, ionic) {
var readyCallbacks = [];
var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
function domReady() {
isDomReady = true;
for (var x = 0; x < readyCallbacks.length; x++) {
ionic.requestAnimationFrame(readyCallbacks[x]);
}
readyCallbacks = [];
document.removeEventListener('DOMContentLoaded', domReady);
}
if (!isDomReady) {
document.addEventListener('DOMContentLoaded', domReady);
}
// From the man himself, Mr. Paul Irish.
// The requestAnimationFrame polyfill
// Put it on window just to preserve its context
// without having to use .call
window._rAF = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 16);
};
})();
var cancelAnimationFrame = window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.webkitCancelRequestAnimationFrame;
/**
* @ngdoc utility
* @name ionic.DomUtil
* @module ionic
*/
ionic.DomUtil = {
//Call with proper context
/**
* @ngdoc method
* @name ionic.DomUtil#requestAnimationFrame
* @alias ionic.requestAnimationFrame
* @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
* @param {function} callback The function to call when the next frame
* happens.
*/
requestAnimationFrame: function(cb) {
return window._rAF(cb);
},
cancelAnimationFrame: function(requestId) {
cancelAnimationFrame(requestId);
},
/**
* @ngdoc method
* @name ionic.DomUtil#animationFrameThrottle
* @alias ionic.animationFrameThrottle
* @description
* When given a callback, if that callback is called 100 times between
* animation frames, adding Throttle will make it only run the last of
* the 100 calls.
*
* @param {function} callback a function which will be throttled to
* requestAnimationFrame
* @returns {function} A function which will then call the passed in callback.
* The passed in callback will receive the context the returned function is
* called with.
*/
animationFrameThrottle: function(cb) {
var args, isQueued, context;
return function() {
args = arguments;
context = this;
if (!isQueued) {
isQueued = true;
ionic.requestAnimationFrame(function() {
cb.apply(context, args);
isQueued = false;
});
}
};
},
contains: function(parentNode, otherNode) {
var current = otherNode;
while (current) {
if (current === parentNode) return true;
current = current.parentNode;
}
},
/**
* @ngdoc method
* @name ionic.DomUtil#getPositionInParent
* @description
* Find an element's scroll offset within its container.
* @param {DOMElement} element The element to find the offset of.
* @returns {object} A position object with the following properties:
* - `{number}` `left` The left offset of the element.
* - `{number}` `top` The top offset of the element.
*/
getPositionInParent: function(el) {
return {
left: el.offsetLeft,
top: el.offsetTop
};
},
getOffsetTop: function(el) {
var curtop = 0;
if (el.offsetParent) {
do {
curtop += el.offsetTop;
el = el.offsetParent;
} while (el)
return curtop;
}
},
/**
* @ngdoc method
* @name ionic.DomUtil#ready
* @description
* Call a function when the DOM is ready, or if it is already ready
* call the function immediately.
* @param {function} callback The function to be called.
*/
ready: function(cb) {
if (isDomReady) {
ionic.requestAnimationFrame(cb);
} else {
readyCallbacks.push(cb);
}
},
/**
* @ngdoc method
* @name ionic.DomUtil#getTextBounds
* @description
* Get a rect representing the bounds of the given textNode.
* @param {DOMElement} textNode The textNode to find the bounds of.
* @returns {object} An object representing the bounds of the node. Properties:
* - `{number}` `left` The left position of the textNode.
* - `{number}` `right` The right position of the textNode.
* - `{number}` `top` The top position of the textNode.
* - `{number}` `bottom` The bottom position of the textNode.
* - `{number}` `width` The width of the textNode.
* - `{number}` `height` The height of the textNode.
*/
getTextBounds: function(textNode) {
if (document.createRange) {
var range = document.createRange();
range.selectNodeContents(textNode);
if (range.getBoundingClientRect) {
var rect = range.getBoundingClientRect();
if (rect) {
var sx = window.scrollX;
var sy = window.scrollY;
return {
top: rect.top + sy,
left: rect.left + sx,
right: rect.left + sx + rect.width,
bottom: rect.top + sy + rect.height,
width: rect.width,
height: rect.height
};
}
}
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getChildIndex
* @description
* Get the first index of a child node within the given element of the
* specified type.
* @param {DOMElement} element The element to find the index of.
* @param {string} type The nodeName to match children of element against.
* @returns {number} The index, or -1, of a child with nodeName matching type.
*/
getChildIndex: function(element, type) {
if (type) {
var ch = element.parentNode.children;
var c;
for (var i = 0, k = 0, j = ch.length; i < j; i++) {
c = ch[i];
if (c.nodeName && c.nodeName.toLowerCase() == type) {
if (c == element) {
return k;
}
k++;
}
}
}
return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
},
/**
* @private
*/
swapNodes: function(src, dest) {
dest.parentNode.insertBefore(src, dest);
},
elementIsDescendant: function(el, parent, stopAt) {
var current = el;
do {
if (current === parent) return true;
current = current.parentNode;
} while (current && current !== stopAt);
return false;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getParentWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent of element matching the
* className, or null.
*/
getParentWithClass: function(e, className, depth) {
depth = depth || 10;
while (e.parentNode && depth--) {
if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
return e.parentNode;
}
e = e.parentNode;
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getParentOrSelfWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent or self matching the
* className, or null.
*/
getParentOrSelfWithClass: function(e, className, depth) {
depth = depth || 10;
while (e && depth--) {
if (e.classList && e.classList.contains(className)) {
return e;
}
e = e.parentNode;
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#rectContains
* @param {number} x
* @param {number} y
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @returns {boolean} Whether {x,y} fits within the rectangle defined by
* {x1,y1,x2,y2}.
*/
rectContains: function(x, y, x1, y1, x2, y2) {
if (x < x1 || x > x2) return false;
if (y < y1 || y > y2) return false;
return true;
},
/**
* @ngdoc method
* @name ionic.DomUtil#blurAll
* @description
* Blurs any currently focused input element
* @returns {DOMElement} The element blurred or null
*/
blurAll: function() {
if (document.activeElement && document.activeElement != document.body) {
document.activeElement.blur();
return document.activeElement;
}
return null;
},
cachedAttr: function(ele, key, value) {
ele = ele && ele.length && ele[0] || ele;
if (ele && ele.setAttribute) {
var dataKey = '$attr-' + key;
if (arguments.length > 2) {
if (ele[dataKey] !== value) {
ele.setAttribute(key, value);
ele[dataKey] = value;
}
} else if (typeof ele[dataKey] == 'undefined') {
ele[dataKey] = ele.getAttribute(key);
}
return ele[dataKey];
}
},
cachedStyles: function(ele, styles) {
ele = ele && ele.length && ele[0] || ele;
if (ele && ele.style) {
for (var prop in styles) {
if (ele['$style-' + prop] !== styles[prop]) {
ele.style[prop] = ele['$style-' + prop] = styles[prop];
}
}
}
}
};
//Shortcuts
ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
})(window, document, ionic);
/**
* ion-events.js
*
* Author: Max Lynch <max@drifty.com>
*
* Framework events handles various mobile browser events, and
* detects special events like tap/swipe/etc. and emits them
* as custom events that can be used in an app.
*
* Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
*/
(function(ionic) {
// Custom event polyfill
ionic.CustomEvent = (function() {
if( typeof window.CustomEvent === 'function' ) return CustomEvent;
var customEvent = function(event, params) {
var evt;
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
try {
evt = document.createEvent("CustomEvent");
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
} catch (error) {
// fallback for browsers that don't support createEvent('CustomEvent')
evt = document.createEvent("Event");
for (var param in params) {
evt[param] = params[param];
}
evt.initEvent(event, params.bubbles, params.cancelable);
}
return evt;
};
customEvent.prototype = window.Event.prototype;
return customEvent;
})();
/**
* @ngdoc utility
* @name ionic.EventController
* @module ionic
*/
ionic.EventController = {
VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
/**
* @ngdoc method
* @name ionic.EventController#trigger
* @alias ionic.trigger
* @param {string} eventType The event to trigger.
* @param {object} data The data for the event. Hint: pass in
* `{target: targetElement}`
* @param {boolean=} bubbles Whether the event should bubble up the DOM.
* @param {boolean=} cancelable Whether the event should be cancelable.
*/
// Trigger a new event
trigger: function(eventType, data, bubbles, cancelable) {
var event = new ionic.CustomEvent(eventType, {
detail: data,
bubbles: !!bubbles,
cancelable: !!cancelable
});
// 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 && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
},
/**
* @ngdoc method
* @name ionic.EventController#on
* @alias ionic.on
* @description Listen to an event on an element.
* @param {string} type The event to listen for.
* @param {function} callback The listener to be called.
* @param {DOMElement} element The element to listen for the event on.
*/
on: function(type, callback, element) {
var e = element || window;
// Bind a gesture if it's a virtual event
for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
if(type == this.VIRTUALIZED_EVENTS[i]) {
var gesture = new ionic.Gesture(element);
gesture.on(type, callback);
return gesture;
}
}
// Otherwise bind a normal event
e.addEventListener(type, callback);
},
/**
* @ngdoc method
* @name ionic.EventController#off
* @alias ionic.off
* @description Remove an event listener.
* @param {string} type
* @param {function} callback
* @param {DOMElement} element
*/
off: function(type, callback, element) {
element.removeEventListener(type, callback);
},
/**
* @ngdoc method
* @name ionic.EventController#onGesture
* @alias ionic.onGesture
* @description Add an event listener for a gesture on an element.
*
* Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
*
* `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
* `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
* `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, </br>
* `touch`, `release`
*
* @param {string} eventType The gesture event to listen for.
* @param {function(e)} callback The function to call when the gesture
* happens.
* @param {DOMElement} element The angular element to listen for the event on.
* @param {object} options object.
* @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
*/
onGesture: function(type, callback, element, options) {
var gesture = new ionic.Gesture(element, options);
gesture.on(type, callback);
return gesture;
},
/**
* @ngdoc method
* @name ionic.EventController#offGesture
* @alias ionic.offGesture
* @description Remove an event listener for a gesture created on an element.
* @param {ionic.Gesture} gesture The gesture that should be removed.
* @param {string} eventType The gesture event to remove the listener for.
* @param {function(e)} callback The listener to remove.
*/
offGesture: function(gesture, type, callback) {
gesture && gesture.off(type, callback);
},
handlePopState: function() {}
};
// Map some convenient top-level functions for event handling
ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
})(window.ionic);
/* eslint camelcase:0 */
/**
* Simple gesture controllers with some common gestures that emit
* gesture events.
*
* Ported from github.com/EightMedia/hammer.js Gestures - thanks!
*/
(function(ionic) {
/**
* ionic.Gestures
* use this to create instances
* @param {HTMLElement} element
* @param {Object} options
* @returns {ionic.Gestures.Instance}
* @constructor
*/
ionic.Gesture = function(element, options) {
return new ionic.Gestures.Instance(element, options || {});
};
ionic.Gestures = {};
// default settings
ionic.Gestures.defaults = {
// add css 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: 'disable-user-behavior'
};
// detect touchevents
ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
// dont use mouseevents on mobile devices
ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
// eventtypes per touchevent (start, move, end)
// are filled by ionic.Gestures.event.determineEventTypes on setup
ionic.Gestures.EVENT_TYPES = {};
// direction defines
ionic.Gestures.DIRECTION_DOWN = 'down';
ionic.Gestures.DIRECTION_LEFT = 'left';
ionic.Gestures.DIRECTION_UP = 'up';
ionic.Gestures.DIRECTION_RIGHT = 'right';
// pointer type
ionic.Gestures.POINTER_MOUSE = 'mouse';
ionic.Gestures.POINTER_TOUCH = 'touch';
ionic.Gestures.POINTER_PEN = 'pen';
// touch event defines
ionic.Gestures.EVENT_START = 'start';
ionic.Gestures.EVENT_MOVE = 'move';
ionic.Gestures.EVENT_END = 'end';
// hammer document where the base events are added at
ionic.Gestures.DOCUMENT = window.document;
// plugins namespace
ionic.Gestures.plugins = {};
// if the window events are set...
ionic.Gestures.READY = false;
/**
* setup events to detect gestures on the document
*/
function setup() {
if(ionic.Gestures.READY) {
return;
}
// find what eventtypes we add listeners to
ionic.Gestures.event.determineEventTypes();
// Register all gestures inside ionic.Gestures.gestures
for(var name in ionic.Gestures.gestures) {
if(ionic.Gestures.gestures.hasOwnProperty(name)) {
ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
}
}
// Add touch events on the document
ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
// ionic.Gestures is ready...!
ionic.Gestures.READY = true;
}
/**
* create new hammer instance
* all methods should return the instance itself, so it is chainable.
* @param {HTMLElement} element
* @param {Object} [options={}]
* @returns {ionic.Gestures.Instance}
* @name Gesture.Instance
* @constructor
*/
ionic.Gestures.Instance = function(element, options) {
var self = this;
// A null element was passed into the instance, which means
// whatever lookup was done to find this element failed to find it
// so we can't listen for events on it.
if(element === null) {
void 0;
return this;
}
// setup ionic.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 = ionic.Gestures.utils.extend(
ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
options || {});
// add some css to the element to prevent the browser from doing its native behavoir
if(this.options.stop_browser_behavior) {
ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
}
// start detection on touchstart
ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
if(self.enabled) {
ionic.Gestures.detection.startDetect(self, ev);
}
});
// return instance
return this;
};
ionic.Gestures.Instance.prototype = {
/**
* bind events to the instance
* @param {String} gesture
* @param {Function} handler
* @returns {ionic.Gestures.Instance}
*/
on: function onEvent(gesture, handler){
var gestures = gesture.split(' ');
for(var t = 0; t < gestures.length; t++) {
this.element.addEventListener(gestures[t], handler, false);
}
return this;
},
/**
* unbind events to the instance
* @param {String} gesture
* @param {Function} handler
* @returns {ionic.Gestures.Instance}
*/
off: function offEvent(gesture, handler){
var gestures = gesture.split(' ');
for(var t = 0; t < gestures.length; t++) {
this.element.removeEventListener(gestures[t], handler, false);
}
return this;
},
/**
* trigger gesture event
* @param {String} gesture
* @param {Object} eventData
* @returns {ionic.Gestures.Instance}
*/
trigger: function triggerEvent(gesture, eventData){
// create DOM event
var event = ionic.Gestures.DOCUMENT.createEvent('Event');
event.initEvent(gesture, true, true);
event.gesture = eventData;
// trigger on the target if it is in the instance element,
// this is for event delegation tricks
var element = this.element;
if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
element = eventData.target;
}
element.dispatchEvent(event);
return this;
},
/**
* enable of disable hammer.js detection
* @param {Boolean} state
* @returns {ionic.Gestures.Instance}
*/
enable: function enable(state) {
this.enabled = state;
return this;
}
};
/**
* this holds the last move event,
* used to fix empty touchend issue
* see the onTouch event for an explanation
* type {Object}
*/
var last_move_event = null;
/**
* when the mouse is hold down, this is true
* type {Boolean}
*/
var enable_detect = false;
/**
* when touch events have been fired, this is true
* type {Boolean}
*/
var touch_triggered = false;
ionic.Gestures.event = {
/**
* simple addEventListener
* @param {HTMLElement} element
* @param {String} type
* @param {Function} handler
*/
bindDom: function(element, type, handler) {
var types = type.split(' ');
for(var t = 0; t < types.length; t++) {
element.addEventListener(types[t], handler, false);
}
},
/**
* touch events with mouse fallback
* @param {HTMLElement} element
* @param {String} eventType like ionic.Gestures.EVENT_MOVE
* @param {Function} handler
*/
onTouch: function onTouch(element, eventType, handler) {
var self = this;
this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
var sourceEventType = ev.type.toLowerCase();
// onmouseup, but when touchend has been fired we do nothing.
// this is for touchdevices which also fire a mouseup on touchend
if(sourceEventType.match(/mouse/) && touch_triggered) {
return;
}
// mousebutton must be down or a touch event
else if( sourceEventType.match(/touch/) || // touch events are always on screen
sourceEventType.match(/pointerdown/) || // pointerevents touch
(sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed
){
enable_detect = true;
}
// mouse isn't pressed
else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
enable_detect = false;
}
// we are in a touch event, set the touch triggered bool to true,
// this for the conflicts that may occur on ios and android
if(sourceEventType.match(/touch|pointer/)) {
touch_triggered = true;
}
// count the total touches on the screen
var count_touches = 0;
// when touch has been triggered in this detection session
// and we are now handling a mouse event, we stop that to prevent conflicts
if(enable_detect) {
// update pointerevent
if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
}
// touch
else if(sourceEventType.match(/touch/)) {
count_touches = ev.touches.length;
}
// mouse
else if(!touch_triggered) {
count_touches = sourceEventType.match(/up/) ? 0 : 1;
}
// if we are in a end event, but when we remove one touch and
// we still have enough, set eventType to move
if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
eventType = ionic.Gestures.EVENT_MOVE;
}
// no touches, force the end event
else if(!count_touches) {
eventType = ionic.Gestures.EVENT_END;
}
// store the last move event
if(count_touches || last_move_event === null) {
last_move_event = ev;
}
// trigger the handler
handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
// remove pointerevent from list
if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
count_touches = ionic.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;
ionic.Gestures.PointerEvent.reset();
}
});
},
/**
* we have different events for each device/browser
* determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
*/
determineEventTypes: function determineEventTypes() {
// determine the eventtype we want to set
var types;
// pointerEvents magic
if(ionic.Gestures.HAS_POINTEREVENTS) {
types = ionic.Gestures.PointerEvent.getEvents();
}
// on Android, iOS, blackberry, windows mobile we dont want any mouseevents
else if(ionic.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'];
}
ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
ionic.Gestures.EVENT_TYPES[ionic.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(ionic.Gestures.HAS_POINTEREVENTS) {
return ionic.Gestures.PointerEvent.getTouchList();
}
// get the touchlist
else if(ev.touches) {
return ev.touches;
}
// make fake touchlist from mouse position
else {
ev.identifier = 1;
return [ev];
}
},
/**
* collect event data for ionic.Gestures js
* @param {HTMLElement} element
* @param {String} eventType like ionic.Gestures.EVENT_MOVE
* @param {Object} eventData
*/
collectEventData: function collectEventData(element, eventType, touches, ev) {
// find out pointerType
var pointerType = ionic.Gestures.POINTER_TOUCH;
if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
pointerType = ionic.Gestures.POINTER_MOUSE;
}
return {
center: ionic.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 ionic.Gestures.detection.stopDetect();
}
};
}
};
ionic.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 ionic.Gestures.EVENT_END
* @param {Object} pointerEvent
*/
updatePointer: function(type, pointerEvent) {
if(type == ionic.Gestures.EVENT_END) {
this.pointers = {};
}
else {
pointerEvent.identifier = pointerEvent.pointerId;
this.pointers[pointerEvent.pointerId] = pointerEvent;
}
return Object.keys(this.pointers).length;
},
/**
* check if ev matches pointertype
* @param {String} pointerType ionic.Gestures.POINTER_MOUSE
* @param {PointerEvent} ev
*/
matchType: function(pointerType, ev) {
if(!ev.pointerType) {
return false;
}
var types = {};
types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.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 = {};
}
};
ionic.Gestures.utils = {
/**
* extend method,
* also used for cloning when dest is an empty object
* @param {Object} dest
* @param {Object} src
* @param {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
* @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 < len; t++) {
valuesX.push(touches[t].pageX);
valuesY.push(touches[t].pageY);
}
return {
pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
};
},
/**
* calculate the velocity between two points
* @param {Number} delta_time
* @param {Number} delta_x
* @param {Number} delta_y
* @returns {Object} velocity
*/
getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
return {
x: Math.abs(delta_x / delta_time) || 0,
y: Math.abs(delta_y / delta_time) || 0
};
},
/**
* calculate the angle between two coordinates
* @param {Touch} touch1
* @param {Touch} touch2
* @returns {Number} angle
*/
getAngle: function getAngle(touch1, touch2) {
var y = touch2.pageY - touch1.pageY,
x = touch2.pageX - touch1.pageX;
return Math.atan2(y, x) * 180 / Math.PI;
},
/**
* angle to direction define
* @param {Touch} touch1
* @param {Touch} touch2
* @returns {String} direction constant, like ionic.Gestures.DIRECTION_LEFT
*/
getDirection: function getDirection(touch1, touch2) {
var x = Math.abs(touch1.pageX - touch2.pageX),
y = Math.abs(touch1.pageY - touch2.pageY);
if(x >= y) {
return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
}
else {
return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.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 == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
},
/**
* stop browser default behavior with css class
* @param {HtmlElement} element
* @param {Object} css_class
*/
stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
// changed from making many style changes to just adding a preset classname
// less DOM manipulations, less code, and easier to control in the CSS side of things
// hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
if(element && element.classList) {
element.classList.add(css_class);
element.onselectstart = function() {
return false;
};
}
}
};
ionic.Gestures.detection = {
// contains all registred ionic.Gestures.gestures in the correct order
gestures: [],
// data of the current ionic.Gestures.gesture detection session
current: null,
// the previous ionic.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 ionic.Gestures.gesture detection
* @param {ionic.Gestures.Instance} inst
* @param {Object} eventData
*/
startDetect: function startDetect(inst, eventData) {
// already busy with a ionic.Gestures.gesture detection on an element
if(this.current) {
return;
}
this.stopped = false;
this.current = {
inst: inst, // reference to ionic.GesturesInstance we're working for
startEvent: ionic.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);
},
/**
* ionic.Gestures.gesture detection
* @param {Object} eventData
*/
detect: function detect(eventData) {
if(!this.current || this.stopped) {
return null;
}
// extend event data with calculations about scale, distance etc
eventData = this.extendEventData(eventData);
// instance options
var inst_options = this.current.inst.options;
// call ionic.Gestures.gesture handlers
for(var g = 0, len = this.gestures.length; g < len; g++) {
var gesture = this.gestures[g];
// only when the instance options have enabled this gesture
if(!this.stopped && inst_options[gesture.name] !== false) {
// if a handler returns false, we stop with the detection
if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
this.stopDetect();
break;
}
}
}
// store as previous event event
if(this.current) {
this.current.lastEvent = eventData;
}
// endevent, but not the last touch, so dont stop
if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
this.stopDetect();
}
return eventData;
},
/**
* clear the ionic.Gestures.gesture vars
* this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
* to stop other ionic.Gestures.gestures from being fired
*/
stopDetect: function stopDetect() {
// clone current data to the store as the previous gesture
// used for the double tap gesture, since this is an other gesture detect session
this.previous = ionic.Gestures.utils.extend({}, this.current);
// reset the current
this.current = null;
// stopped!
this.stopped = true;
},
/**
* extend eventData for ionic.Gestures.gestures
* @param {Object} ev
* @returns {Object} ev
*/
extendEventData: function extendEventData(ev) {
var startEv = this.current.startEvent;
// if the touches change, set the new touches over the startEvent touches
// this because touchevents don't have all the touches on touchstart, or the
// user must place his fingers at the EXACT same time on the screen, which is not realistic
// but, sometimes it happens that both fingers are touching at the EXACT same time
if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
// extend 1 level deep to get the touchlist with the touch objects
startEv.touches = [];
for(var i = 0, len = ev.touches.length; i < len; i++) {
startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
}
}
var delta_time = ev.timeStamp - startEv.timeStamp,
delta_x = ev.center.pageX - startEv.center.pageX,
delta_y = ev.center.pageY - startEv.center.pageY,
velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
ionic.Gestures.utils.extend(ev, {
deltaTime: delta_time,
deltaX: delta_x,
deltaY: delta_y,
velocityX: velocity.x,
velocityY: velocity.y,
distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
startEvent: startEv
});
return ev;
},
/**
* register new gesture
* @param {Object} gesture object, see gestures.js for documentation
* @returns {Array} gestures
*/
register: function register(gesture) {
// add an enable gesture options if there is no given
var options = gesture.defaults || {};
if(options[gesture.name] === undefined) {
options[gesture.name] = true;
}
// extend ionic.Gestures default options with the ionic.Gestures.gesture options
ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
// set its index
gesture.index = gesture.index || 1000;
// add ionic.Gestures.gesture to the list
this.gestures.push(gesture);
// sort the list by index
this.gestures.sort(function(a, b) {
if (a.index < b.index) {
return -1;
}
if (a.index > b.index) {
return 1;
}
return 0;
});
return this.gestures;
}
};
ionic.Gestures.gestures = ionic.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 ionic.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 ionic.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